You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SecurityMiddlewareTest.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. <?php
  2. /**
  3. * @author Bernhard Posselt <dev@bernhard-posselt.com>
  4. * @author Lukas Reschke <lukas@owncloud.com>
  5. *
  6. * @copyright Copyright (c) 2015, ownCloud, Inc.
  7. * @license AGPL-3.0
  8. *
  9. * This code is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License, version 3,
  11. * as published by the Free Software Foundation.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License, version 3,
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>
  20. *
  21. */
  22. namespace Test\AppFramework\Middleware\Security;
  23. use OC\AppFramework\Http;
  24. use OC\AppFramework\Http\Request;
  25. use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException;
  26. use OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException;
  27. use OC\AppFramework\Middleware\Security\Exceptions\NotAdminException;
  28. use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException;
  29. use OC\AppFramework\Middleware\Security\Exceptions\SecurityException;
  30. use OC\Appframework\Middleware\Security\Exceptions\StrictCookieMissingException;
  31. use OC\AppFramework\Middleware\Security\SecurityMiddleware;
  32. use OC\AppFramework\Utility\ControllerMethodReflector;
  33. use OC\Settings\AuthorizedGroupMapper;
  34. use OCP\App\IAppManager;
  35. use OCP\AppFramework\Controller;
  36. use OCP\AppFramework\Http\JSONResponse;
  37. use OCP\AppFramework\Http\RedirectResponse;
  38. use OCP\AppFramework\Http\TemplateResponse;
  39. use OCP\IConfig;
  40. use OCP\IL10N;
  41. use OCP\INavigationManager;
  42. use OCP\IRequest;
  43. use OCP\IURLGenerator;
  44. use OCP\IUserSession;
  45. use OCP\Security\ISecureRandom;
  46. use Psr\Log\LoggerInterface;
  47. class SecurityMiddlewareTest extends \Test\TestCase {
  48. /** @var SecurityMiddleware|\PHPUnit\Framework\MockObject\MockObject */
  49. private $middleware;
  50. /** @var Controller|\PHPUnit\Framework\MockObject\MockObject */
  51. private $controller;
  52. /** @var SecurityException */
  53. private $secException;
  54. /** @var SecurityException */
  55. private $secAjaxException;
  56. /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
  57. private $request;
  58. /** @var ControllerMethodReflector */
  59. private $reader;
  60. /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
  61. private $logger;
  62. /** @var INavigationManager|\PHPUnit\Framework\MockObject\MockObject */
  63. private $navigationManager;
  64. /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
  65. private $urlGenerator;
  66. /** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject */
  67. private $appManager;
  68. /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
  69. private $l10n;
  70. /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */
  71. private $userSession;
  72. /** @var AuthorizedGroupMapper|\PHPUnit\Framework\MockObject\MockObject */
  73. private $authorizedGroupMapper;
  74. protected function setUp(): void {
  75. parent::setUp();
  76. $this->authorizedGroupMapper = $this->createMock(AuthorizedGroupMapper::class);
  77. $this->userSession = $this->createMock(IUserSession::class);
  78. $this->controller = $this->createMock(Controller::class);
  79. $this->reader = new ControllerMethodReflector();
  80. $this->logger = $this->createMock(LoggerInterface::class);
  81. $this->navigationManager = $this->createMock(INavigationManager::class);
  82. $this->urlGenerator = $this->createMock(IURLGenerator::class);
  83. $this->request = $this->createMock(IRequest::class);
  84. $this->l10n = $this->createMock(IL10N::class);
  85. $this->middleware = $this->getMiddleware(true, true, false);
  86. $this->secException = new SecurityException('hey', false);
  87. $this->secAjaxException = new SecurityException('hey', true);
  88. }
  89. private function getMiddleware(bool $isLoggedIn, bool $isAdminUser, bool $isSubAdmin, bool $isAppEnabledForUser = true): SecurityMiddleware {
  90. $this->appManager = $this->createMock(IAppManager::class);
  91. $this->appManager->expects($this->any())
  92. ->method('isEnabledForUser')
  93. ->willReturn($isAppEnabledForUser);
  94. return new SecurityMiddleware(
  95. $this->request,
  96. $this->reader,
  97. $this->navigationManager,
  98. $this->urlGenerator,
  99. $this->logger,
  100. 'files',
  101. $isLoggedIn,
  102. $isAdminUser,
  103. $isSubAdmin,
  104. $this->appManager,
  105. $this->l10n,
  106. $this->authorizedGroupMapper,
  107. $this->userSession
  108. );
  109. }
  110. /**
  111. * @PublicPage
  112. * @NoCSRFRequired
  113. */
  114. public function testSetNavigationEntry() {
  115. $this->navigationManager->expects($this->once())
  116. ->method('setActiveEntry')
  117. ->with($this->equalTo('files'));
  118. $this->reader->reflect(__CLASS__, __FUNCTION__);
  119. $this->middleware->beforeController($this->controller, __FUNCTION__);
  120. }
  121. /**
  122. * @param string $method
  123. * @param string $test
  124. */
  125. private function ajaxExceptionStatus($method, $test, $status) {
  126. $isLoggedIn = false;
  127. $isAdminUser = false;
  128. // isAdminUser requires isLoggedIn call to return true
  129. if ($test === 'isAdminUser') {
  130. $isLoggedIn = true;
  131. }
  132. $sec = $this->getMiddleware($isLoggedIn, $isAdminUser, false);
  133. try {
  134. $this->reader->reflect(__CLASS__, $method);
  135. $sec->beforeController($this->controller, $method);
  136. } catch (SecurityException $ex) {
  137. $this->assertEquals($status, $ex->getCode());
  138. }
  139. // add assertion if everything should work fine otherwise phpunit will
  140. // complain
  141. if ($status === 0) {
  142. $this->addToAssertionCount(1);
  143. }
  144. }
  145. public function testAjaxStatusLoggedInCheck() {
  146. $this->ajaxExceptionStatus(
  147. __FUNCTION__,
  148. 'isLoggedIn',
  149. Http::STATUS_UNAUTHORIZED
  150. );
  151. }
  152. /**
  153. * @NoCSRFRequired
  154. */
  155. public function testAjaxNotAdminCheck() {
  156. $this->ajaxExceptionStatus(
  157. __FUNCTION__,
  158. 'isAdminUser',
  159. Http::STATUS_FORBIDDEN
  160. );
  161. }
  162. /**
  163. * @PublicPage
  164. */
  165. public function testAjaxStatusCSRFCheck() {
  166. $this->ajaxExceptionStatus(
  167. __FUNCTION__,
  168. 'passesCSRFCheck',
  169. Http::STATUS_PRECONDITION_FAILED
  170. );
  171. }
  172. /**
  173. * @PublicPage
  174. * @NoCSRFRequired
  175. */
  176. public function testAjaxStatusAllGood() {
  177. $this->ajaxExceptionStatus(
  178. __FUNCTION__,
  179. 'isLoggedIn',
  180. 0
  181. );
  182. $this->ajaxExceptionStatus(
  183. __FUNCTION__,
  184. 'isAdminUser',
  185. 0
  186. );
  187. $this->ajaxExceptionStatus(
  188. __FUNCTION__,
  189. 'passesCSRFCheck',
  190. 0
  191. );
  192. }
  193. /**
  194. * @PublicPage
  195. * @NoCSRFRequired
  196. */
  197. public function testNoChecks() {
  198. $this->request->expects($this->never())
  199. ->method('passesCSRFCheck')
  200. ->willReturn(false);
  201. $sec = $this->getMiddleware(false, false, false);
  202. $this->reader->reflect(__CLASS__, __FUNCTION__);
  203. $sec->beforeController($this->controller, __FUNCTION__);
  204. }
  205. /**
  206. * @param string $method
  207. * @param string $expects
  208. */
  209. private function securityCheck($method, $expects, $shouldFail = false) {
  210. // admin check requires login
  211. if ($expects === 'isAdminUser') {
  212. $isLoggedIn = true;
  213. $isAdminUser = !$shouldFail;
  214. } else {
  215. $isLoggedIn = !$shouldFail;
  216. $isAdminUser = false;
  217. }
  218. $sec = $this->getMiddleware($isLoggedIn, $isAdminUser, false);
  219. if ($shouldFail) {
  220. $this->expectException(SecurityException::class);
  221. } else {
  222. $this->addToAssertionCount(1);
  223. }
  224. $this->reader->reflect(__CLASS__, $method);
  225. $sec->beforeController($this->controller, $method);
  226. }
  227. /**
  228. * @PublicPage
  229. */
  230. public function testCsrfCheck() {
  231. $this->expectException(\OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException::class);
  232. $this->request->expects($this->once())
  233. ->method('passesCSRFCheck')
  234. ->willReturn(false);
  235. $this->request->expects($this->once())
  236. ->method('passesStrictCookieCheck')
  237. ->willReturn(true);
  238. $this->reader->reflect(__CLASS__, __FUNCTION__);
  239. $this->middleware->beforeController($this->controller, __FUNCTION__);
  240. }
  241. /**
  242. * @PublicPage
  243. * @NoCSRFRequired
  244. */
  245. public function testNoCsrfCheck() {
  246. $this->request->expects($this->never())
  247. ->method('passesCSRFCheck')
  248. ->willReturn(false);
  249. $this->reader->reflect(__CLASS__, __FUNCTION__);
  250. $this->middleware->beforeController($this->controller, __FUNCTION__);
  251. }
  252. /**
  253. * @PublicPage
  254. */
  255. public function testPassesCsrfCheck() {
  256. $this->request->expects($this->once())
  257. ->method('passesCSRFCheck')
  258. ->willReturn(true);
  259. $this->request->expects($this->once())
  260. ->method('passesStrictCookieCheck')
  261. ->willReturn(true);
  262. $this->reader->reflect(__CLASS__, __FUNCTION__);
  263. $this->middleware->beforeController($this->controller, __FUNCTION__);
  264. }
  265. /**
  266. * @PublicPage
  267. */
  268. public function testFailCsrfCheck() {
  269. $this->expectException(\OC\AppFramework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException::class);
  270. $this->request->expects($this->once())
  271. ->method('passesCSRFCheck')
  272. ->willReturn(false);
  273. $this->request->expects($this->once())
  274. ->method('passesStrictCookieCheck')
  275. ->willReturn(true);
  276. $this->reader->reflect(__CLASS__, __FUNCTION__);
  277. $this->middleware->beforeController($this->controller, __FUNCTION__);
  278. }
  279. /**
  280. * @PublicPage
  281. * @StrictCookieRequired
  282. */
  283. public function testStrictCookieRequiredCheck() {
  284. $this->expectException(\OC\Appframework\Middleware\Security\Exceptions\StrictCookieMissingException::class);
  285. $this->request->expects($this->never())
  286. ->method('passesCSRFCheck');
  287. $this->request->expects($this->once())
  288. ->method('passesStrictCookieCheck')
  289. ->willReturn(false);
  290. $this->reader->reflect(__CLASS__, __FUNCTION__);
  291. $this->middleware->beforeController($this->controller, __FUNCTION__);
  292. }
  293. /**
  294. * @PublicPage
  295. * @NoCSRFRequired
  296. */
  297. public function testNoStrictCookieRequiredCheck() {
  298. $this->request->expects($this->never())
  299. ->method('passesStrictCookieCheck')
  300. ->willReturn(false);
  301. $this->reader->reflect(__CLASS__, __FUNCTION__);
  302. $this->middleware->beforeController($this->controller, __FUNCTION__);
  303. }
  304. /**
  305. * @PublicPage
  306. * @NoCSRFRequired
  307. * @StrictCookieRequired
  308. */
  309. public function testPassesStrictCookieRequiredCheck() {
  310. $this->request
  311. ->expects($this->once())
  312. ->method('passesStrictCookieCheck')
  313. ->willReturn(true);
  314. $this->reader->reflect(__CLASS__, __FUNCTION__);
  315. $this->middleware->beforeController($this->controller, __FUNCTION__);
  316. }
  317. public function dataCsrfOcsController() {
  318. $controller = $this->getMockBuilder('OCP\AppFramework\Controller')
  319. ->disableOriginalConstructor()
  320. ->getMock();
  321. $ocsController = $this->getMockBuilder('OCP\AppFramework\OCSController')
  322. ->disableOriginalConstructor()
  323. ->getMock();
  324. return [
  325. [$controller, false, false, true],
  326. [$controller, false, true, true],
  327. [$controller, true, false, true],
  328. [$controller, true, true, true],
  329. [$ocsController, false, false, true],
  330. [$ocsController, false, true, false],
  331. [$ocsController, true, false, false],
  332. [$ocsController, true, true, false],
  333. ];
  334. }
  335. /**
  336. * @dataProvider dataCsrfOcsController
  337. * @param Controller $controller
  338. * @param bool $hasOcsApiHeader
  339. * @param bool $hasBearerAuth
  340. * @param bool $exception
  341. */
  342. public function testCsrfOcsController(Controller $controller, bool $hasOcsApiHeader, bool $hasBearerAuth, bool $exception) {
  343. $this->request
  344. ->method('getHeader')
  345. ->willReturnCallback(function ($header) use ($hasOcsApiHeader, $hasBearerAuth) {
  346. if ($header === 'OCS-APIREQUEST' && $hasOcsApiHeader) {
  347. return 'true';
  348. }
  349. if ($header === 'Authorization' && $hasBearerAuth) {
  350. return 'Bearer TOKEN!';
  351. }
  352. return '';
  353. });
  354. $this->request->expects($this->once())
  355. ->method('passesStrictCookieCheck')
  356. ->willReturn(true);
  357. try {
  358. $this->middleware->beforeController($controller, 'foo');
  359. $this->assertFalse($exception);
  360. } catch (CrossSiteRequestForgeryException $e) {
  361. $this->assertTrue($exception);
  362. }
  363. }
  364. /**
  365. * @NoCSRFRequired
  366. * @NoAdminRequired
  367. */
  368. public function testLoggedInCheck() {
  369. $this->securityCheck(__FUNCTION__, 'isLoggedIn');
  370. }
  371. /**
  372. * @NoCSRFRequired
  373. * @NoAdminRequired
  374. */
  375. public function testFailLoggedInCheck() {
  376. $this->securityCheck(__FUNCTION__, 'isLoggedIn', true);
  377. }
  378. /**
  379. * @NoCSRFRequired
  380. */
  381. public function testIsAdminCheck() {
  382. $this->securityCheck(__FUNCTION__, 'isAdminUser');
  383. }
  384. /**
  385. * @NoCSRFRequired
  386. * @SubAdminRequired
  387. */
  388. public function testIsNotSubAdminCheck() {
  389. $this->reader->reflect(__CLASS__, __FUNCTION__);
  390. $sec = $this->getMiddleware(true, false, false);
  391. $this->expectException(SecurityException::class);
  392. $sec->beforeController($this, __METHOD__);
  393. }
  394. /**
  395. * @NoCSRFRequired
  396. * @SubAdminRequired
  397. */
  398. public function testIsSubAdminCheck() {
  399. $this->reader->reflect(__CLASS__, __FUNCTION__);
  400. $sec = $this->getMiddleware(true, false, true);
  401. $sec->beforeController($this, __METHOD__);
  402. $this->addToAssertionCount(1);
  403. }
  404. /**
  405. * @NoCSRFRequired
  406. * @SubAdminRequired
  407. */
  408. public function testIsSubAdminAndAdminCheck() {
  409. $this->reader->reflect(__CLASS__, __FUNCTION__);
  410. $sec = $this->getMiddleware(true, true, true);
  411. $sec->beforeController($this, __METHOD__);
  412. $this->addToAssertionCount(1);
  413. }
  414. /**
  415. * @NoCSRFRequired
  416. */
  417. public function testFailIsAdminCheck() {
  418. $this->securityCheck(__FUNCTION__, 'isAdminUser', true);
  419. }
  420. public function testAfterExceptionNotCaughtThrowsItAgain() {
  421. $ex = new \Exception();
  422. $this->expectException(\Exception::class);
  423. $this->middleware->afterException($this->controller, 'test', $ex);
  424. }
  425. public function testAfterExceptionReturnsRedirectForNotLoggedInUser() {
  426. $this->request = new Request(
  427. [
  428. 'server' =>
  429. [
  430. 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  431. 'REQUEST_URI' => 'nextcloud/index.php/apps/specialapp'
  432. ]
  433. ],
  434. $this->createMock(ISecureRandom::class),
  435. $this->createMock(IConfig::class)
  436. );
  437. $this->middleware = $this->getMiddleware(false, false, false);
  438. $this->urlGenerator
  439. ->expects($this->once())
  440. ->method('linkToRoute')
  441. ->with(
  442. 'core.login.showLoginForm',
  443. [
  444. 'redirect_url' => 'nextcloud/index.php/apps/specialapp',
  445. ]
  446. )
  447. ->willReturn('http://localhost/nextcloud/index.php/login?redirect_url=nextcloud/index.php/apps/specialapp');
  448. $this->logger
  449. ->expects($this->once())
  450. ->method('debug');
  451. $response = $this->middleware->afterException(
  452. $this->controller,
  453. 'test',
  454. new NotLoggedInException()
  455. );
  456. $expected = new RedirectResponse('http://localhost/nextcloud/index.php/login?redirect_url=nextcloud/index.php/apps/specialapp');
  457. $this->assertEquals($expected, $response);
  458. }
  459. public function testAfterExceptionRedirectsToWebRootAfterStrictCookieFail() {
  460. $this->request = new Request(
  461. [
  462. 'server' => [
  463. 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  464. 'REQUEST_URI' => 'nextcloud/index.php/apps/specialapp',
  465. ],
  466. ],
  467. $this->createMock(ISecureRandom::class),
  468. $this->createMock(IConfig::class)
  469. );
  470. $this->middleware = $this->getMiddleware(false, false, false);
  471. $response = $this->middleware->afterException(
  472. $this->controller,
  473. 'test',
  474. new StrictCookieMissingException()
  475. );
  476. $expected = new RedirectResponse(\OC::$WEBROOT . '/');
  477. $this->assertEquals($expected, $response);
  478. }
  479. /**
  480. * @return array
  481. */
  482. public function exceptionProvider() {
  483. return [
  484. [
  485. new AppNotEnabledException(),
  486. ],
  487. [
  488. new CrossSiteRequestForgeryException(),
  489. ],
  490. [
  491. new NotAdminException(''),
  492. ],
  493. ];
  494. }
  495. /**
  496. * @dataProvider exceptionProvider
  497. * @param SecurityException $exception
  498. */
  499. public function testAfterExceptionReturnsTemplateResponse(SecurityException $exception) {
  500. $this->request = new Request(
  501. [
  502. 'server' =>
  503. [
  504. 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  505. 'REQUEST_URI' => 'nextcloud/index.php/apps/specialapp'
  506. ]
  507. ],
  508. $this->createMock(ISecureRandom::class),
  509. $this->createMock(IConfig::class)
  510. );
  511. $this->middleware = $this->getMiddleware(false, false, false);
  512. $this->logger
  513. ->expects($this->once())
  514. ->method('debug');
  515. $response = $this->middleware->afterException(
  516. $this->controller,
  517. 'test',
  518. $exception
  519. );
  520. $expected = new TemplateResponse('core', '403', ['message' => $exception->getMessage()], 'guest');
  521. $expected->setStatus($exception->getCode());
  522. $this->assertEquals($expected, $response);
  523. }
  524. public function testAfterAjaxExceptionReturnsJSONError() {
  525. $response = $this->middleware->afterException($this->controller, 'test',
  526. $this->secAjaxException);
  527. $this->assertTrue($response instanceof JSONResponse);
  528. }
  529. public function dataRestrictedApp() {
  530. return [
  531. [false, false, false,],
  532. [false, false, true,],
  533. [false, true, false,],
  534. [false, true, true,],
  535. [ true, false, false,],
  536. [ true, false, true,],
  537. [ true, true, false,],
  538. [ true, true, true,],
  539. ];
  540. }
  541. /**
  542. * @PublicPage
  543. * @NoAdminRequired
  544. * @NoCSRFRequired
  545. */
  546. public function testRestrictedAppLoggedInPublicPage() {
  547. $middleware = $this->getMiddleware(true, false, false);
  548. $this->reader->reflect(__CLASS__, __FUNCTION__);
  549. $this->appManager->method('getAppPath')
  550. ->with('files')
  551. ->willReturn('foo');
  552. $this->appManager->method('isEnabledForUser')
  553. ->with('files')
  554. ->willReturn(false);
  555. $middleware->beforeController($this->controller, __FUNCTION__);
  556. $this->addToAssertionCount(1);
  557. }
  558. /**
  559. * @PublicPage
  560. * @NoAdminRequired
  561. * @NoCSRFRequired
  562. */
  563. public function testRestrictedAppNotLoggedInPublicPage() {
  564. $middleware = $this->getMiddleware(false, false, false);
  565. $this->reader->reflect(__CLASS__, __FUNCTION__);
  566. $this->appManager->method('getAppPath')
  567. ->with('files')
  568. ->willReturn('foo');
  569. $this->appManager->method('isEnabledForUser')
  570. ->with('files')
  571. ->willReturn(false);
  572. $middleware->beforeController($this->controller, __FUNCTION__);
  573. $this->addToAssertionCount(1);
  574. }
  575. /**
  576. * @NoAdminRequired
  577. * @NoCSRFRequired
  578. */
  579. public function testRestrictedAppLoggedIn() {
  580. $middleware = $this->getMiddleware(true, false, false, false);
  581. $this->reader->reflect(__CLASS__, __FUNCTION__);
  582. $this->appManager->method('getAppPath')
  583. ->with('files')
  584. ->willReturn('foo');
  585. $this->expectException(AppNotEnabledException::class);
  586. $middleware->beforeController($this->controller, __FUNCTION__);
  587. }
  588. }