ClientFlowLoginControllerTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
  4. *
  5. * @license GNU AGPL version 3 or any later version
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as
  9. * published by the Free Software Foundation, either version 3 of the
  10. * License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. namespace Tests\Core\Controller;
  22. use OC\Authentication\Exceptions\InvalidTokenException;
  23. use OC\Authentication\Exceptions\PasswordlessTokenException;
  24. use OC\Authentication\Token\IProvider;
  25. use OC\Authentication\Token\IToken;
  26. use OC\Core\Controller\ClientFlowLoginController;
  27. use OCA\OAuth2\Db\AccessTokenMapper;
  28. use OCA\OAuth2\Db\Client;
  29. use OCA\OAuth2\Db\ClientMapper;
  30. use OCP\AppFramework\Http;
  31. use OCP\AppFramework\Http\TemplateResponse;
  32. use OCP\Defaults;
  33. use OCP\IL10N;
  34. use OCP\IRequest;
  35. use OCP\ISession;
  36. use OCP\IURLGenerator;
  37. use OCP\IUser;
  38. use OCP\IUserSession;
  39. use OCP\Security\ICrypto;
  40. use OCP\Security\ISecureRandom;
  41. use OCP\Session\Exceptions\SessionNotAvailableException;
  42. use Test\TestCase;
  43. class ClientFlowLoginControllerTest extends TestCase {
  44. /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
  45. private $request;
  46. /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
  47. private $userSession;
  48. /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
  49. private $l10n;
  50. /** @var Defaults|\PHPUnit_Framework_MockObject_MockObject */
  51. private $defaults;
  52. /** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
  53. private $session;
  54. /** @var IProvider|\PHPUnit_Framework_MockObject_MockObject */
  55. private $tokenProvider;
  56. /** @var ISecureRandom|\PHPUnit_Framework_MockObject_MockObject */
  57. private $random;
  58. /** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
  59. private $urlGenerator;
  60. /** @var ClientMapper|\PHPUnit_Framework_MockObject_MockObject */
  61. private $clientMapper;
  62. /** @var AccessTokenMapper|\PHPUnit_Framework_MockObject_MockObject */
  63. private $accessTokenMapper;
  64. /** @var ICrypto|\PHPUnit_Framework_MockObject_MockObject */
  65. private $crypto;
  66. /** @var ClientFlowLoginController */
  67. private $clientFlowLoginController;
  68. public function setUp() {
  69. parent::setUp();
  70. $this->request = $this->createMock(IRequest::class);
  71. $this->userSession = $this->createMock(IUserSession::class);
  72. $this->l10n = $this->createMock(IL10N::class);
  73. $this->l10n
  74. ->expects($this->any())
  75. ->method('t')
  76. ->will($this->returnCallback(function($text, $parameters = array()) {
  77. return vsprintf($text, $parameters);
  78. }));
  79. $this->defaults = $this->createMock(Defaults::class);
  80. $this->session = $this->createMock(ISession::class);
  81. $this->tokenProvider = $this->createMock(IProvider::class);
  82. $this->random = $this->createMock(ISecureRandom::class);
  83. $this->urlGenerator = $this->createMock(IURLGenerator::class);
  84. $this->clientMapper = $this->createMock(ClientMapper::class);
  85. $this->accessTokenMapper = $this->createMock(AccessTokenMapper::class);
  86. $this->crypto = $this->createMock(ICrypto::class);
  87. $this->clientFlowLoginController = new ClientFlowLoginController(
  88. 'core',
  89. $this->request,
  90. $this->userSession,
  91. $this->l10n,
  92. $this->defaults,
  93. $this->session,
  94. $this->tokenProvider,
  95. $this->random,
  96. $this->urlGenerator,
  97. $this->clientMapper,
  98. $this->accessTokenMapper,
  99. $this->crypto
  100. );
  101. }
  102. public function testShowAuthPickerPageNoClientOrOauthRequest() {
  103. $expected = new TemplateResponse(
  104. 'core',
  105. 'error',
  106. [
  107. 'errors' =>
  108. [
  109. [
  110. 'error' => 'Access Forbidden',
  111. 'hint' => 'Invalid request',
  112. ],
  113. ],
  114. ],
  115. 'guest'
  116. );
  117. $this->assertEquals($expected, $this->clientFlowLoginController->showAuthPickerPage());
  118. }
  119. public function testShowAuthPickerPageWithOcsHeader() {
  120. $this->request
  121. ->expects($this->at(0))
  122. ->method('getHeader')
  123. ->with('USER_AGENT')
  124. ->willReturn('Mac OS X Sync Client');
  125. $this->request
  126. ->expects($this->at(1))
  127. ->method('getHeader')
  128. ->with('OCS-APIREQUEST')
  129. ->willReturn('true');
  130. $this->random
  131. ->expects($this->once())
  132. ->method('generate')
  133. ->with(
  134. 64,
  135. ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
  136. )
  137. ->willReturn('StateToken');
  138. $this->session
  139. ->expects($this->once())
  140. ->method('set')
  141. ->with('client.flow.state.token', 'StateToken');
  142. $this->session
  143. ->expects($this->once())
  144. ->method('get')
  145. ->with('oauth.state')
  146. ->willReturn('OauthStateToken');
  147. $this->defaults
  148. ->expects($this->once())
  149. ->method('getName')
  150. ->willReturn('ExampleCloud');
  151. $this->request
  152. ->expects($this->once())
  153. ->method('getServerHost')
  154. ->willReturn('example.com');
  155. $this->request
  156. ->method('getServerProtocol')
  157. ->willReturn('https');
  158. $expected = new TemplateResponse(
  159. 'core',
  160. 'loginflow/authpicker',
  161. [
  162. 'client' => 'Mac OS X Sync Client',
  163. 'clientIdentifier' => '',
  164. 'instanceName' => 'ExampleCloud',
  165. 'urlGenerator' => $this->urlGenerator,
  166. 'stateToken' => 'StateToken',
  167. 'serverHost' => 'https://example.com',
  168. 'oauthState' => 'OauthStateToken',
  169. ],
  170. 'guest'
  171. );
  172. $this->assertEquals($expected, $this->clientFlowLoginController->showAuthPickerPage());
  173. }
  174. public function testShowAuthPickerPageWithOauth() {
  175. $this->request
  176. ->expects($this->at(0))
  177. ->method('getHeader')
  178. ->with('USER_AGENT')
  179. ->willReturn('Mac OS X Sync Client');
  180. $client = new Client();
  181. $client->setName('My external service');
  182. $this->clientMapper
  183. ->expects($this->once())
  184. ->method('getByIdentifier')
  185. ->with('MyClientIdentifier')
  186. ->willReturn($client);
  187. $this->random
  188. ->expects($this->once())
  189. ->method('generate')
  190. ->with(
  191. 64,
  192. ISecureRandom::CHAR_LOWER.ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS
  193. )
  194. ->willReturn('StateToken');
  195. $this->session
  196. ->expects($this->once())
  197. ->method('set')
  198. ->with('client.flow.state.token', 'StateToken');
  199. $this->session
  200. ->expects($this->once())
  201. ->method('get')
  202. ->with('oauth.state')
  203. ->willReturn('OauthStateToken');
  204. $this->defaults
  205. ->expects($this->once())
  206. ->method('getName')
  207. ->willReturn('ExampleCloud');
  208. $this->request
  209. ->expects($this->once())
  210. ->method('getServerHost')
  211. ->willReturn('example.com');
  212. $this->request
  213. ->method('getServerProtocol')
  214. ->willReturn('https');
  215. $expected = new TemplateResponse(
  216. 'core',
  217. 'loginflow/authpicker',
  218. [
  219. 'client' => 'My external service',
  220. 'clientIdentifier' => 'MyClientIdentifier',
  221. 'instanceName' => 'ExampleCloud',
  222. 'urlGenerator' => $this->urlGenerator,
  223. 'stateToken' => 'StateToken',
  224. 'serverHost' => 'https://example.com',
  225. 'oauthState' => 'OauthStateToken',
  226. ],
  227. 'guest'
  228. );
  229. $this->assertEquals($expected, $this->clientFlowLoginController->showAuthPickerPage('MyClientIdentifier'));
  230. }
  231. public function testGenerateAppPasswordWithInvalidToken() {
  232. $this->session
  233. ->expects($this->once())
  234. ->method('get')
  235. ->with('client.flow.state.token')
  236. ->willReturn('OtherToken');
  237. $this->session
  238. ->expects($this->once())
  239. ->method('remove')
  240. ->with('client.flow.state.token');
  241. $expected = new TemplateResponse(
  242. 'core',
  243. '403',
  244. [
  245. 'file' => 'State token does not match',
  246. ],
  247. 'guest'
  248. );
  249. $expected->setStatus(Http::STATUS_FORBIDDEN);
  250. $this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
  251. }
  252. public function testGenerateAppPasswordWithSessionNotAvailableException() {
  253. $this->session
  254. ->expects($this->once())
  255. ->method('get')
  256. ->with('client.flow.state.token')
  257. ->willReturn('MyStateToken');
  258. $this->session
  259. ->expects($this->once())
  260. ->method('remove')
  261. ->with('client.flow.state.token');
  262. $this->session
  263. ->expects($this->once())
  264. ->method('getId')
  265. ->willThrowException(new SessionNotAvailableException());
  266. $expected = new Http\Response();
  267. $expected->setStatus(Http::STATUS_FORBIDDEN);
  268. $this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
  269. }
  270. public function testGenerateAppPasswordWithInvalidTokenException() {
  271. $this->session
  272. ->expects($this->once())
  273. ->method('get')
  274. ->with('client.flow.state.token')
  275. ->willReturn('MyStateToken');
  276. $this->session
  277. ->expects($this->once())
  278. ->method('remove')
  279. ->with('client.flow.state.token');
  280. $this->session
  281. ->expects($this->once())
  282. ->method('getId')
  283. ->willReturn('SessionId');
  284. $this->tokenProvider
  285. ->expects($this->once())
  286. ->method('getToken')
  287. ->with('SessionId')
  288. ->willThrowException(new InvalidTokenException());
  289. $expected = new Http\Response();
  290. $expected->setStatus(Http::STATUS_FORBIDDEN);
  291. $this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
  292. }
  293. public function testGeneratePasswordWithPassword() {
  294. $this->session
  295. ->expects($this->once())
  296. ->method('get')
  297. ->with('client.flow.state.token')
  298. ->willReturn('MyStateToken');
  299. $this->session
  300. ->expects($this->once())
  301. ->method('remove')
  302. ->with('client.flow.state.token');
  303. $this->session
  304. ->expects($this->once())
  305. ->method('getId')
  306. ->willReturn('SessionId');
  307. $myToken = $this->createMock(IToken::class);
  308. $myToken
  309. ->expects($this->once())
  310. ->method('getLoginName')
  311. ->willReturn('MyLoginName');
  312. $this->tokenProvider
  313. ->expects($this->once())
  314. ->method('getToken')
  315. ->with('SessionId')
  316. ->willReturn($myToken);
  317. $this->tokenProvider
  318. ->expects($this->once())
  319. ->method('getPassword')
  320. ->with($myToken, 'SessionId')
  321. ->willReturn('MyPassword');
  322. $this->random
  323. ->expects($this->once())
  324. ->method('generate')
  325. ->with(72)
  326. ->willReturn('MyGeneratedToken');
  327. $user = $this->createMock(IUser::class);
  328. $user
  329. ->expects($this->once())
  330. ->method('getUID')
  331. ->willReturn('MyUid');
  332. $this->userSession
  333. ->expects($this->once())
  334. ->method('getUser')
  335. ->willReturn($user);
  336. $this->tokenProvider
  337. ->expects($this->once())
  338. ->method('generateToken')
  339. ->with(
  340. 'MyGeneratedToken',
  341. 'MyUid',
  342. 'MyLoginName',
  343. 'MyPassword',
  344. 'unknown',
  345. IToken::PERMANENT_TOKEN,
  346. IToken::DO_NOT_REMEMBER
  347. );
  348. $this->request
  349. ->expects($this->once())
  350. ->method('getServerProtocol')
  351. ->willReturn('http');
  352. $this->request
  353. ->expects($this->once())
  354. ->method('getServerHost')
  355. ->willReturn('example.com');
  356. $this->request
  357. ->expects($this->any())
  358. ->method('getHeader')
  359. ->willReturn('');
  360. $expected = new Http\RedirectResponse('nc://login/server:http://example.com&user:MyLoginName&password:MyGeneratedToken');
  361. $this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
  362. }
  363. public function testGeneratePasswordWithPasswordForOauthClient() {
  364. $this->session
  365. ->expects($this->at(0))
  366. ->method('get')
  367. ->with('client.flow.state.token')
  368. ->willReturn('MyStateToken');
  369. $this->session
  370. ->expects($this->at(1))
  371. ->method('remove')
  372. ->with('client.flow.state.token');
  373. $this->session
  374. ->expects($this->at(3))
  375. ->method('get')
  376. ->with('oauth.state')
  377. ->willReturn('MyOauthState');
  378. $this->session
  379. ->expects($this->at(4))
  380. ->method('remove')
  381. ->with('oauth.state');
  382. $this->session
  383. ->expects($this->once())
  384. ->method('getId')
  385. ->willReturn('SessionId');
  386. $myToken = $this->createMock(IToken::class);
  387. $myToken
  388. ->expects($this->once())
  389. ->method('getLoginName')
  390. ->willReturn('MyLoginName');
  391. $this->tokenProvider
  392. ->expects($this->once())
  393. ->method('getToken')
  394. ->with('SessionId')
  395. ->willReturn($myToken);
  396. $this->tokenProvider
  397. ->expects($this->once())
  398. ->method('getPassword')
  399. ->with($myToken, 'SessionId')
  400. ->willReturn('MyPassword');
  401. $this->random
  402. ->expects($this->at(0))
  403. ->method('generate')
  404. ->with(72)
  405. ->willReturn('MyGeneratedToken');
  406. $this->random
  407. ->expects($this->at(1))
  408. ->method('generate')
  409. ->with(128)
  410. ->willReturn('MyAccessCode');
  411. $user = $this->createMock(IUser::class);
  412. $user
  413. ->expects($this->once())
  414. ->method('getUID')
  415. ->willReturn('MyUid');
  416. $this->userSession
  417. ->expects($this->once())
  418. ->method('getUser')
  419. ->willReturn($user);
  420. $token = $this->createMock(IToken::class);
  421. $this->tokenProvider
  422. ->expects($this->once())
  423. ->method('generateToken')
  424. ->with(
  425. 'MyGeneratedToken',
  426. 'MyUid',
  427. 'MyLoginName',
  428. 'MyPassword',
  429. 'My OAuth client',
  430. IToken::PERMANENT_TOKEN,
  431. IToken::DO_NOT_REMEMBER
  432. )
  433. ->willReturn($token);
  434. $client = new Client();
  435. $client->setName('My OAuth client');
  436. $client->setRedirectUri('https://example.com/redirect.php');
  437. $this->clientMapper
  438. ->expects($this->once())
  439. ->method('getByIdentifier')
  440. ->with('MyClientIdentifier')
  441. ->willReturn($client);
  442. $expected = new Http\RedirectResponse('https://example.com/redirect.php?state=MyOauthState&code=MyAccessCode');
  443. $this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken', 'MyClientIdentifier'));
  444. }
  445. public function testGeneratePasswordWithoutPassword() {
  446. $this->session
  447. ->expects($this->once())
  448. ->method('get')
  449. ->with('client.flow.state.token')
  450. ->willReturn('MyStateToken');
  451. $this->session
  452. ->expects($this->once())
  453. ->method('remove')
  454. ->with('client.flow.state.token');
  455. $this->session
  456. ->expects($this->once())
  457. ->method('getId')
  458. ->willReturn('SessionId');
  459. $myToken = $this->createMock(IToken::class);
  460. $myToken
  461. ->expects($this->once())
  462. ->method('getLoginName')
  463. ->willReturn('MyLoginName');
  464. $this->tokenProvider
  465. ->expects($this->once())
  466. ->method('getToken')
  467. ->with('SessionId')
  468. ->willReturn($myToken);
  469. $this->tokenProvider
  470. ->expects($this->once())
  471. ->method('getPassword')
  472. ->with($myToken, 'SessionId')
  473. ->willThrowException(new PasswordlessTokenException());
  474. $this->random
  475. ->expects($this->once())
  476. ->method('generate')
  477. ->with(72)
  478. ->willReturn('MyGeneratedToken');
  479. $user = $this->createMock(IUser::class);
  480. $user
  481. ->expects($this->once())
  482. ->method('getUID')
  483. ->willReturn('MyUid');
  484. $this->userSession
  485. ->expects($this->once())
  486. ->method('getUser')
  487. ->willReturn($user);
  488. $this->tokenProvider
  489. ->expects($this->once())
  490. ->method('generateToken')
  491. ->with(
  492. 'MyGeneratedToken',
  493. 'MyUid',
  494. 'MyLoginName',
  495. null,
  496. 'unknown',
  497. IToken::PERMANENT_TOKEN,
  498. IToken::DO_NOT_REMEMBER
  499. );
  500. $this->request
  501. ->expects($this->once())
  502. ->method('getServerProtocol')
  503. ->willReturn('http');
  504. $this->request
  505. ->expects($this->once())
  506. ->method('getServerHost')
  507. ->willReturn('example.com');
  508. $this->request
  509. ->expects($this->any())
  510. ->method('getHeader')
  511. ->willReturn('');
  512. $expected = new Http\RedirectResponse('nc://login/server:http://example.com&user:MyLoginName&password:MyGeneratedToken');
  513. $this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
  514. }
  515. public function dataGeneratePasswordWithHttpsProxy() {
  516. return [
  517. [
  518. [
  519. ['X-Forwarded-Proto', 'http'],
  520. ['X-Forwarded-Ssl', 'off'],
  521. ['USER_AGENT', ''],
  522. ],
  523. 'http',
  524. 'http',
  525. ],
  526. [
  527. [
  528. ['X-Forwarded-Proto', 'http'],
  529. ['X-Forwarded-Ssl', 'off'],
  530. ['USER_AGENT', ''],
  531. ],
  532. 'https',
  533. 'https',
  534. ],
  535. [
  536. [
  537. ['X-Forwarded-Proto', 'https'],
  538. ['X-Forwarded-Ssl', 'off'],
  539. ['USER_AGENT', ''],
  540. ],
  541. 'http',
  542. 'https',
  543. ],
  544. [
  545. [
  546. ['X-Forwarded-Proto', 'https'],
  547. ['X-Forwarded-Ssl', 'on'],
  548. ['USER_AGENT', ''],
  549. ],
  550. 'http',
  551. 'https',
  552. ],
  553. [
  554. [
  555. ['X-Forwarded-Proto', 'http'],
  556. ['X-Forwarded-Ssl', 'on'],
  557. ['USER_AGENT', ''],
  558. ],
  559. 'http',
  560. 'https',
  561. ],
  562. ];
  563. }
  564. /**
  565. * @dataProvider dataGeneratePasswordWithHttpsProxy
  566. * @param array $headers
  567. * @param string $protocol
  568. * @param string $expected
  569. */
  570. public function testGeneratePasswordWithHttpsProxy(array $headers, $protocol, $expected) {
  571. $this->session
  572. ->expects($this->once())
  573. ->method('get')
  574. ->with('client.flow.state.token')
  575. ->willReturn('MyStateToken');
  576. $this->session
  577. ->expects($this->once())
  578. ->method('remove')
  579. ->with('client.flow.state.token');
  580. $this->session
  581. ->expects($this->once())
  582. ->method('getId')
  583. ->willReturn('SessionId');
  584. $myToken = $this->createMock(IToken::class);
  585. $myToken
  586. ->expects($this->once())
  587. ->method('getLoginName')
  588. ->willReturn('MyLoginName');
  589. $this->tokenProvider
  590. ->expects($this->once())
  591. ->method('getToken')
  592. ->with('SessionId')
  593. ->willReturn($myToken);
  594. $this->tokenProvider
  595. ->expects($this->once())
  596. ->method('getPassword')
  597. ->with($myToken, 'SessionId')
  598. ->willReturn('MyPassword');
  599. $this->random
  600. ->expects($this->once())
  601. ->method('generate')
  602. ->with(72)
  603. ->willReturn('MyGeneratedToken');
  604. $user = $this->createMock(IUser::class);
  605. $user
  606. ->expects($this->once())
  607. ->method('getUID')
  608. ->willReturn('MyUid');
  609. $this->userSession
  610. ->expects($this->once())
  611. ->method('getUser')
  612. ->willReturn($user);
  613. $this->tokenProvider
  614. ->expects($this->once())
  615. ->method('generateToken')
  616. ->with(
  617. 'MyGeneratedToken',
  618. 'MyUid',
  619. 'MyLoginName',
  620. 'MyPassword',
  621. 'unknown',
  622. IToken::PERMANENT_TOKEN,
  623. IToken::DO_NOT_REMEMBER
  624. );
  625. $this->request
  626. ->expects($this->once())
  627. ->method('getServerProtocol')
  628. ->willReturn($protocol);
  629. $this->request
  630. ->expects($this->once())
  631. ->method('getServerHost')
  632. ->willReturn('example.com');
  633. $this->request
  634. ->expects($this->atLeastOnce())
  635. ->method('getHeader')
  636. ->willReturnMap($headers);
  637. $expected = new Http\RedirectResponse('nc://login/server:' . $expected . '://example.com&user:MyLoginName&password:MyGeneratedToken');
  638. $this->assertEquals($expected, $this->clientFlowLoginController->generateAppPassword('MyStateToken'));
  639. }
  640. }