OauthApiControllerTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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 OCA\OAuth2\Tests\Controller;
  22. use OC\Authentication\Exceptions\InvalidTokenException;
  23. use OC\Authentication\Token\DefaultToken;
  24. use OC\Authentication\Token\DefaultTokenMapper;
  25. use OC\Authentication\Token\ExpiredTokenException;
  26. use OC\Authentication\Token\IProvider as TokenProvider;
  27. use OC\Authentication\Token\IToken;
  28. use OCA\OAuth2\Controller\OauthApiController;
  29. use OCA\OAuth2\Db\AccessToken;
  30. use OCA\OAuth2\Db\AccessTokenMapper;
  31. use OCA\OAuth2\Db\Client;
  32. use OCA\OAuth2\Db\ClientMapper;
  33. use OCA\OAuth2\Exceptions\AccessTokenNotFoundException;
  34. use OCA\OAuth2\Exceptions\ClientNotFoundException;
  35. use OCP\AppFramework\Http;
  36. use OCP\AppFramework\Http\JSONResponse;
  37. use OCP\AppFramework\Utility\ITimeFactory;
  38. use OCP\IRequest;
  39. use OCP\Security\ICrypto;
  40. use OCP\Security\ISecureRandom;
  41. use Test\TestCase;
  42. class OauthApiControllerTest extends TestCase {
  43. /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
  44. private $request;
  45. /** @var ICrypto|\PHPUnit_Framework_MockObject_MockObject */
  46. private $crypto;
  47. /** @var AccessTokenMapper|\PHPUnit_Framework_MockObject_MockObject */
  48. private $accessTokenMapper;
  49. /** @var ClientMapper|\PHPUnit_Framework_MockObject_MockObject */
  50. private $clientMapper;
  51. /** @var TokenProvider|\PHPUnit_Framework_MockObject_MockObject */
  52. private $tokenProvider;
  53. /** @var ISecureRandom|\PHPUnit_Framework_MockObject_MockObject */
  54. private $secureRandom;
  55. /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
  56. private $time;
  57. /** @var OauthApiController */
  58. private $oauthApiController;
  59. public function setUp() {
  60. parent::setUp();
  61. $this->request = $this->createMock(IRequest::class);
  62. $this->crypto = $this->createMock(ICrypto::class);
  63. $this->accessTokenMapper = $this->createMock(AccessTokenMapper::class);
  64. $this->clientMapper = $this->createMock(ClientMapper::class);
  65. $this->tokenProvider = $this->createMock(TokenProvider::class);
  66. $this->secureRandom = $this->createMock(ISecureRandom::class);
  67. $this->time = $this->createMock(ITimeFactory::class);
  68. $this->oauthApiController = new OauthApiController(
  69. 'oauth2',
  70. $this->request,
  71. $this->crypto,
  72. $this->accessTokenMapper,
  73. $this->clientMapper,
  74. $this->tokenProvider,
  75. $this->secureRandom,
  76. $this->time
  77. );
  78. }
  79. public function testGetTokenInvalidGrantType() {
  80. $expected = new JSONResponse([
  81. 'error' => 'invalid_grant',
  82. ], Http::STATUS_BAD_REQUEST);
  83. $this->assertEquals($expected, $this->oauthApiController->getToken('foo', null, null, null, null));
  84. }
  85. public function testGetTokenInvalidCode() {
  86. $expected = new JSONResponse([
  87. 'error' => 'invalid_request',
  88. ], Http::STATUS_BAD_REQUEST);
  89. $this->accessTokenMapper->method('getByCode')
  90. ->with('invalidcode')
  91. ->willThrowException(new AccessTokenNotFoundException());
  92. $this->assertEquals($expected, $this->oauthApiController->getToken('authorization_code', 'invalidcode', null, null, null));
  93. }
  94. public function testGetTokenInvalidRefreshToken() {
  95. $expected = new JSONResponse([
  96. 'error' => 'invalid_request',
  97. ], Http::STATUS_BAD_REQUEST);
  98. $this->accessTokenMapper->method('getByCode')
  99. ->with('invalidrefresh')
  100. ->willThrowException(new AccessTokenNotFoundException());
  101. $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'invalidrefresh', null, null));
  102. }
  103. public function testGetTokenClientDoesNotExist() {
  104. $expected = new JSONResponse([
  105. 'error' => 'invalid_request',
  106. ], Http::STATUS_BAD_REQUEST);
  107. $accessToken = new AccessToken();
  108. $accessToken->setClientId(42);
  109. $this->accessTokenMapper->method('getByCode')
  110. ->with('validrefresh')
  111. ->willReturn($accessToken);
  112. $this->clientMapper->method('getByUid')
  113. ->with(42)
  114. ->willThrowException(new ClientNotFoundException());
  115. $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', null, null));
  116. }
  117. public function invalidClientProvider() {
  118. return [
  119. ['invalidClientId', 'invalidClientSecret'],
  120. ['clientId', 'invalidClientSecret'],
  121. ['invalidClientId', 'clientSecret'],
  122. ];
  123. }
  124. /**
  125. * @dataProvider invalidClientProvider
  126. *
  127. * @param string $clientId
  128. * @param string $clientSecret
  129. */
  130. public function testGetTokenInvalidClient($clientId, $clientSecret) {
  131. $expected = new JSONResponse([
  132. 'error' => 'invalid_client',
  133. ], Http::STATUS_BAD_REQUEST);
  134. $accessToken = new AccessToken();
  135. $accessToken->setClientId(42);
  136. $this->accessTokenMapper->method('getByCode')
  137. ->with('validrefresh')
  138. ->willReturn($accessToken);
  139. $client = new Client();
  140. $client->setClientIdentifier('clientId');
  141. $client->setSecret('clientSecret');
  142. $this->clientMapper->method('getByUid')
  143. ->with(42)
  144. ->willReturn($client);
  145. $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', $clientId, $clientSecret));
  146. }
  147. public function testGetTokenInvalidAppToken() {
  148. $expected = new JSONResponse([
  149. 'error' => 'invalid_request',
  150. ], Http::STATUS_BAD_REQUEST);
  151. $accessToken = new AccessToken();
  152. $accessToken->setClientId(42);
  153. $accessToken->setTokenId(1337);
  154. $accessToken->setEncryptedToken('encryptedToken');
  155. $this->accessTokenMapper->method('getByCode')
  156. ->with('validrefresh')
  157. ->willReturn($accessToken);
  158. $client = new Client();
  159. $client->setClientIdentifier('clientId');
  160. $client->setSecret('clientSecret');
  161. $this->clientMapper->method('getByUid')
  162. ->with(42)
  163. ->willReturn($client);
  164. $this->crypto->method('decrypt')
  165. ->with(
  166. 'encryptedToken',
  167. 'validrefresh'
  168. )->willReturn('decryptedToken');
  169. $this->tokenProvider->method('getTokenById')
  170. ->with(1337)
  171. ->willThrowException(new InvalidTokenException());
  172. $this->accessTokenMapper->expects($this->once())
  173. ->method('delete')
  174. ->with($accessToken);
  175. $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
  176. }
  177. public function testGetTokenValidAppToken() {
  178. $accessToken = new AccessToken();
  179. $accessToken->setClientId(42);
  180. $accessToken->setTokenId(1337);
  181. $accessToken->setEncryptedToken('encryptedToken');
  182. $this->accessTokenMapper->method('getByCode')
  183. ->with('validrefresh')
  184. ->willReturn($accessToken);
  185. $client = new Client();
  186. $client->setClientIdentifier('clientId');
  187. $client->setSecret('clientSecret');
  188. $this->clientMapper->method('getByUid')
  189. ->with(42)
  190. ->willReturn($client);
  191. $this->crypto->method('decrypt')
  192. ->with(
  193. 'encryptedToken',
  194. 'validrefresh'
  195. )->willReturn('decryptedToken');
  196. $appToken = new DefaultToken();
  197. $appToken->setUid('userId');
  198. $this->tokenProvider->method('getTokenById')
  199. ->with(1337)
  200. ->willThrowException(new ExpiredTokenException($appToken));
  201. $this->accessTokenMapper->expects($this->never())
  202. ->method('delete')
  203. ->with($accessToken);
  204. $this->secureRandom->method('generate')
  205. ->will($this->returnCallback(function ($len) {
  206. return 'random'.$len;
  207. }));
  208. $this->tokenProvider->expects($this->once())
  209. ->method('rotate')
  210. ->with(
  211. $appToken,
  212. 'decryptedToken',
  213. 'random72'
  214. )->willReturn($appToken);
  215. $this->time->method('getTime')
  216. ->willReturn(1000);
  217. $this->tokenProvider->expects($this->once())
  218. ->method('updateToken')
  219. ->with(
  220. $this->callback(function (DefaultToken $token) {
  221. return $token->getExpires() === 4600;
  222. })
  223. );
  224. $this->crypto->method('encrypt')
  225. ->with('random72', 'random128')
  226. ->willReturn('newEncryptedToken');
  227. $this->accessTokenMapper->expects($this->once())
  228. ->method('update')
  229. ->with(
  230. $this->callback(function (AccessToken $token) {
  231. return $token->getHashedCode() === hash('sha512', 'random128') &&
  232. $token->getEncryptedToken() === 'newEncryptedToken';
  233. })
  234. );
  235. $expected = new JSONResponse([
  236. 'access_token' => 'random72',
  237. 'token_type' => 'Bearer',
  238. 'expires_in' => 3600,
  239. 'refresh_token' => 'random128',
  240. 'user_id' => 'userId',
  241. ]);
  242. $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
  243. }
  244. public function testGetTokenExpiredAppToken() {
  245. $accessToken = new AccessToken();
  246. $accessToken->setClientId(42);
  247. $accessToken->setTokenId(1337);
  248. $accessToken->setEncryptedToken('encryptedToken');
  249. $this->accessTokenMapper->method('getByCode')
  250. ->with('validrefresh')
  251. ->willReturn($accessToken);
  252. $client = new Client();
  253. $client->setClientIdentifier('clientId');
  254. $client->setSecret('clientSecret');
  255. $this->clientMapper->method('getByUid')
  256. ->with(42)
  257. ->willReturn($client);
  258. $this->crypto->method('decrypt')
  259. ->with(
  260. 'encryptedToken',
  261. 'validrefresh'
  262. )->willReturn('decryptedToken');
  263. $appToken = new DefaultToken();
  264. $appToken->setUid('userId');
  265. $this->tokenProvider->method('getTokenById')
  266. ->with(1337)
  267. ->willReturn($appToken);
  268. $this->accessTokenMapper->expects($this->never())
  269. ->method('delete')
  270. ->with($accessToken);
  271. $this->secureRandom->method('generate')
  272. ->will($this->returnCallback(function ($len) {
  273. return 'random'.$len;
  274. }));
  275. $this->tokenProvider->expects($this->once())
  276. ->method('rotate')
  277. ->with(
  278. $appToken,
  279. 'decryptedToken',
  280. 'random72'
  281. )->willReturn($appToken);
  282. $this->time->method('getTime')
  283. ->willReturn(1000);
  284. $this->tokenProvider->expects($this->once())
  285. ->method('updateToken')
  286. ->with(
  287. $this->callback(function (DefaultToken $token) {
  288. return $token->getExpires() === 4600;
  289. })
  290. );
  291. $this->crypto->method('encrypt')
  292. ->with('random72', 'random128')
  293. ->willReturn('newEncryptedToken');
  294. $this->accessTokenMapper->expects($this->once())
  295. ->method('update')
  296. ->with(
  297. $this->callback(function (AccessToken $token) {
  298. return $token->getHashedCode() === hash('sha512', 'random128') &&
  299. $token->getEncryptedToken() === 'newEncryptedToken';
  300. })
  301. );
  302. $expected = new JSONResponse([
  303. 'access_token' => 'random72',
  304. 'token_type' => 'Bearer',
  305. 'expires_in' => 3600,
  306. 'refresh_token' => 'random128',
  307. 'user_id' => 'userId',
  308. ]);
  309. $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret'));
  310. }
  311. }