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.

1055 lines
30 KiB

  1. <?php
  2. /**
  3. *
  4. * @copyright Copyright (c) 2018, Joachim Bauch (bauch@struktur.de)
  5. *
  6. * @license GNU AGPL version 3 or any later version
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as
  10. * published by the Free Software Foundation, either version 3 of the
  11. * License, or (at your option) any later version.
  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
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. namespace OCA\Talk\Tests\php\Controller;
  23. use OCA\Talk\Chat\CommentsManager;
  24. use OCA\Talk\Config;
  25. use OCA\Talk\Controller\SignalingController;
  26. use OCA\Talk\Events\BeforeSignalingResponseSentEvent;
  27. use OCA\Talk\Exceptions\ParticipantNotFoundException;
  28. use OCA\Talk\Exceptions\RoomNotFoundException;
  29. use OCA\Talk\Federation\Authenticator;
  30. use OCA\Talk\Manager;
  31. use OCA\Talk\Model\Attendee;
  32. use OCA\Talk\Model\AttendeeMapper;
  33. use OCA\Talk\Model\SessionMapper;
  34. use OCA\Talk\Participant;
  35. use OCA\Talk\Room;
  36. use OCA\Talk\Service\CertificateService;
  37. use OCA\Talk\Service\ParticipantService;
  38. use OCA\Talk\Service\RoomService;
  39. use OCA\Talk\Service\SessionService;
  40. use OCA\Talk\Signaling\Messages;
  41. use OCA\Talk\TalkSession;
  42. use OCP\App\IAppManager;
  43. use OCP\AppFramework\Services\IAppConfig;
  44. use OCP\AppFramework\Utility\ITimeFactory;
  45. use OCP\EventDispatcher\IEventDispatcher;
  46. use OCP\Http\Client\IClientService;
  47. use OCP\IConfig;
  48. use OCP\IDBConnection;
  49. use OCP\IGroupManager;
  50. use OCP\IL10N;
  51. use OCP\IRequest;
  52. use OCP\IURLGenerator;
  53. use OCP\IUser;
  54. use OCP\IUserManager;
  55. use OCP\Security\Bruteforce\IThrottler;
  56. use OCP\Security\IHasher;
  57. use OCP\Security\ISecureRandom;
  58. use PHPUnit\Framework\MockObject\MockObject;
  59. use Psr\Log\LoggerInterface;
  60. use Test\TestCase;
  61. class CustomInputSignalingController extends SignalingController {
  62. private $inputStream;
  63. public function setInputStream($data) {
  64. $this->inputStream = $data;
  65. }
  66. protected function getInputStream(): string {
  67. return $this->inputStream;
  68. }
  69. }
  70. /**
  71. * @group DB
  72. */
  73. class SignalingControllerTest extends TestCase {
  74. protected TalkSession&MockObject $session;
  75. protected \OCA\Talk\Signaling\Manager&MockObject $signalingManager;
  76. protected Manager|MockObject $manager;
  77. protected CertificateService&MockObject $certificateService;
  78. protected ParticipantService&MockObject $participantService;
  79. protected SessionService&MockObject $sessionService;
  80. protected Messages&MockObject $messages;
  81. protected IUserManager&MockObject $userManager;
  82. protected ITimeFactory&MockObject $timeFactory;
  83. protected IClientService&MockObject $clientService;
  84. protected IThrottler&MockObject $throttler;
  85. protected LoggerInterface&MockObject $logger;
  86. protected IDBConnection $dbConnection;
  87. protected IConfig $serverConfig;
  88. protected ?Config $config = null;
  89. protected ?string $userId = null;
  90. protected ?ISecureRandom $secureRandom = null;
  91. protected ?IEventDispatcher $dispatcher = null;
  92. private ?CustomInputSignalingController $controller = null;
  93. public function setUp(): void {
  94. parent::setUp();
  95. $this->userId = 'testUser';
  96. $this->secureRandom = \OCP\Server::get(ISecureRandom::class);
  97. /** @var MockObject|IAppConfig $appConfig */
  98. $appConfig = $this->createMock(IAppConfig::class);
  99. $timeFactory = $this->createMock(ITimeFactory::class);
  100. $groupManager = $this->createMock(IGroupManager::class);
  101. $this->serverConfig = \OCP\Server::get(IConfig::class);
  102. $this->serverConfig->setAppValue('spreed', 'signaling_servers', json_encode([
  103. 'secret' => 'MySecretValue',
  104. ]));
  105. $this->serverConfig->setAppValue('spreed', 'signaling_ticket_secret', 'the-app-ticket-secret');
  106. $this->serverConfig->setUserValue($this->userId, 'spreed', 'signaling_ticket_secret', 'the-user-ticket-secret');
  107. $this->userManager = $this->createMock(IUserManager::class);
  108. $this->dispatcher = \OCP\Server::get(IEventDispatcher::class);
  109. $urlGenerator = $this->createMock(IURLGenerator::class);
  110. $this->config = new Config($this->serverConfig, $appConfig, $this->secureRandom, $groupManager, $this->userManager, $urlGenerator, $timeFactory, $this->dispatcher);
  111. $this->session = $this->createMock(TalkSession::class);
  112. $this->dbConnection = \OCP\Server::get(IDBConnection::class);
  113. $this->signalingManager = $this->createMock(\OCA\Talk\Signaling\Manager::class);
  114. $this->manager = $this->createMock(Manager::class);
  115. $this->certificateService = $this->createMock(CertificateService::class);
  116. $this->participantService = $this->createMock(ParticipantService::class);
  117. $this->sessionService = $this->createMock(SessionService::class);
  118. $this->messages = $this->createMock(Messages::class);
  119. $this->timeFactory = $this->createMock(ITimeFactory::class);
  120. $this->clientService = $this->createMock(IClientService::class);
  121. $this->logger = $this->createMock(LoggerInterface::class);
  122. $this->recreateSignalingController();
  123. }
  124. private function recreateSignalingController() {
  125. $this->controller = new CustomInputSignalingController(
  126. 'spreed',
  127. $this->createMock(IRequest::class),
  128. $this->config,
  129. $this->signalingManager,
  130. $this->session,
  131. $this->manager,
  132. $this->certificateService,
  133. $this->participantService,
  134. $this->sessionService,
  135. $this->dbConnection,
  136. $this->messages,
  137. $this->userManager,
  138. $this->dispatcher,
  139. $this->timeFactory,
  140. $this->clientService,
  141. $this->logger,
  142. $this->userId
  143. );
  144. }
  145. private function validateBackendRandom($data, $random, $checksum) {
  146. if (empty($random) || strlen($random) < 32) {
  147. return false;
  148. }
  149. if (empty($checksum)) {
  150. return false;
  151. }
  152. $hash = hash_hmac('sha256', $random . $data, $this->config->getSignalingSecret());
  153. return hash_equals($hash, strtolower($checksum));
  154. }
  155. private function calculateBackendChecksum($data, $random) {
  156. if (empty($random) || strlen($random) < 32) {
  157. return false;
  158. }
  159. $hash = hash_hmac('sha256', $random . $data, $this->config->getSignalingSecret());
  160. return $hash;
  161. }
  162. public function testBackendChecksums(): void {
  163. // Test checksum generation / validation with the example from the API documentation.
  164. $data = '{"type":"auth","auth":{"version":"1.0","params":{"hello":"world"}}}';
  165. $random = 'afb6b872ab03e3376b31bf0af601067222ff7990335ca02d327071b73c0119c6';
  166. $checksum = '3c4a69ff328299803ac2879614b707c807b4758cf19450755c60656cac46e3bc';
  167. $this->assertEquals($checksum, $this->calculateBackendChecksum($data, $random));
  168. $this->assertTrue($this->validateBackendRandom($data, $random, $checksum));
  169. }
  170. private function performBackendRequest($data) {
  171. if (!is_string($data)) {
  172. $data = json_encode($data);
  173. }
  174. $random = 'afb6b872ab03e3376b31bf0af601067222ff7990335ca02d327071b73c0119c6';
  175. $checksum = $this->calculateBackendChecksum($data, $random);
  176. $_SERVER['HTTP_SPREED_SIGNALING_RANDOM'] = $random;
  177. $_SERVER['HTTP_SPREED_SIGNALING_CHECKSUM'] = $checksum;
  178. $this->controller->setInputStream($data);
  179. return $this->controller->backend();
  180. }
  181. public function testBackendChecksumValidation(): void {
  182. $data = '{}';
  183. // Random and checksum missing.
  184. $this->controller->setInputStream($data);
  185. $result = $this->controller->backend();
  186. $this->assertSame([
  187. 'type' => 'error',
  188. 'error' => [
  189. 'code' => 'invalid_request',
  190. 'message' => 'The request could not be authenticated.',
  191. ],
  192. ], $result->getData());
  193. // Invalid checksum.
  194. $this->controller->setInputStream($data);
  195. $random = 'afb6b872ab03e3376b31bf0af601067222ff7990335ca02d327071b73c0119c6';
  196. $checksum = $this->calculateBackendChecksum('{"foo": "bar"}', $random);
  197. $_SERVER['HTTP_SPREED_SIGNALING_RANDOM'] = $random;
  198. $_SERVER['HTTP_SPREED_SIGNALING_CHECKSUM'] = $checksum;
  199. $result = $this->controller->backend();
  200. $this->assertSame([
  201. 'type' => 'error',
  202. 'error' => [
  203. 'code' => 'invalid_request',
  204. 'message' => 'The request could not be authenticated.',
  205. ],
  206. ], $result->getData());
  207. // Short random
  208. $this->controller->setInputStream($data);
  209. $random = '12345';
  210. $checksum = $this->calculateBackendChecksum($data, $random);
  211. $_SERVER['HTTP_SPREED_SIGNALING_RANDOM'] = $random;
  212. $_SERVER['HTTP_SPREED_SIGNALING_CHECKSUM'] = $checksum;
  213. $result = $this->controller->backend();
  214. $this->assertSame([
  215. 'type' => 'error',
  216. 'error' => [
  217. 'code' => 'invalid_request',
  218. 'message' => 'The request could not be authenticated.',
  219. ],
  220. ], $result->getData());
  221. }
  222. public function testBackendUnsupportedType(): void {
  223. $result = $this->performBackendRequest([
  224. 'type' => 'unsupported-type',
  225. ]);
  226. $this->assertSame([
  227. 'type' => 'error',
  228. 'error' => [
  229. 'code' => 'unknown_type',
  230. 'message' => 'The given type {"type":"unsupported-type"} is not supported.',
  231. ],
  232. ], $result->getData());
  233. }
  234. public function testBackendAuth(): void {
  235. // Check validating of tickets.
  236. $result = $this->performBackendRequest([
  237. 'type' => 'auth',
  238. 'auth' => [
  239. 'params' => [
  240. 'userid' => $this->userId,
  241. 'ticket' => 'invalid-ticket',
  242. ],
  243. ],
  244. ]);
  245. $this->assertSame([
  246. 'type' => 'error',
  247. 'error' => [
  248. 'code' => 'invalid_ticket',
  249. 'message' => 'The given ticket is not valid for this user.',
  250. ],
  251. ], $result->getData());
  252. // Check validating ticket for passed user.
  253. $result = $this->performBackendRequest([
  254. 'type' => 'auth',
  255. 'auth' => [
  256. 'params' => [
  257. 'userid' => 'invalid-userid',
  258. 'ticket' => $this->config->getSignalingTicket(Config::SIGNALING_TICKET_V1, $this->userId),
  259. ],
  260. ],
  261. ]);
  262. $this->assertSame([
  263. 'type' => 'error',
  264. 'error' => [
  265. 'code' => 'invalid_ticket',
  266. 'message' => 'The given ticket is not valid for this user.',
  267. ],
  268. ], $result->getData());
  269. // Check validating of existing users.
  270. $result = $this->performBackendRequest([
  271. 'type' => 'auth',
  272. 'auth' => [
  273. 'params' => [
  274. 'userid' => 'unknown-userid',
  275. 'ticket' => $this->config->getSignalingTicket(Config::SIGNALING_TICKET_V1, 'unknown-userid'),
  276. ],
  277. ],
  278. ]);
  279. $this->assertSame([
  280. 'type' => 'error',
  281. 'error' => [
  282. 'code' => 'no_such_user',
  283. 'message' => 'The given user does not exist.',
  284. ],
  285. ], $result->getData());
  286. // Check successfull authentication of users.
  287. $testUser = $this->createMock(IUser::class);
  288. $testUser->expects($this->once())
  289. ->method('getDisplayName')
  290. ->willReturn('Test User');
  291. $testUser->expects($this->once())
  292. ->method('getUID')
  293. ->willReturn($this->userId);
  294. $this->userManager->expects($this->once())
  295. ->method('get')
  296. ->with($this->userId)
  297. ->willReturn($testUser);
  298. $result = $this->performBackendRequest([
  299. 'type' => 'auth',
  300. 'auth' => [
  301. 'params' => [
  302. 'userid' => $this->userId,
  303. 'ticket' => $this->config->getSignalingTicket(Config::SIGNALING_TICKET_V1, $this->userId),
  304. ],
  305. ],
  306. ]);
  307. $this->assertSame([
  308. 'type' => 'auth',
  309. 'auth' => [
  310. 'version' => '1.0',
  311. 'userid' => $this->userId,
  312. 'user' => [
  313. 'displayname' => 'Test User',
  314. ],
  315. ],
  316. ], $result->getData());
  317. // Check successfull authentication of anonymous participants.
  318. $result = $this->performBackendRequest([
  319. 'type' => 'auth',
  320. 'auth' => [
  321. 'params' => [
  322. 'userid' => '',
  323. 'ticket' => $this->config->getSignalingTicket(Config::SIGNALING_TICKET_V1, ''),
  324. ],
  325. ],
  326. ]);
  327. $this->assertSame([
  328. 'type' => 'auth',
  329. 'auth' => [
  330. 'version' => '1.0',
  331. ],
  332. ], $result->getData());
  333. }
  334. public function testBackendRoomUnknown(): void {
  335. $roomToken = 'the-room';
  336. $room = $this->createMock(Room::class);
  337. $this->manager->expects($this->once())
  338. ->method('getRoomByToken')
  339. ->with($roomToken)
  340. ->willThrowException(new RoomNotFoundException());
  341. $result = $this->performBackendRequest([
  342. 'type' => 'room',
  343. 'room' => [
  344. 'roomid' => $roomToken,
  345. 'userid' => $this->userId,
  346. 'sessionid' => '',
  347. ],
  348. ]);
  349. $this->assertSame([
  350. 'type' => 'error',
  351. 'error' => [
  352. 'code' => 'no_such_room',
  353. 'message' => 'The user is not invited to this room.',
  354. ],
  355. ], $result->getData());
  356. }
  357. public function testBackendRoomInvited(): void {
  358. $roomToken = 'the-room';
  359. $roomName = 'the-room-name';
  360. $room = $this->createMock(Room::class);
  361. $this->manager->expects($this->once())
  362. ->method('getRoomByToken')
  363. ->with($roomToken)
  364. ->willReturn($room);
  365. $attendee = Attendee::fromRow([
  366. 'permissions' => Attendee::PERMISSIONS_DEFAULT,
  367. 'actor_type' => Attendee::ACTOR_USERS,
  368. ]);
  369. $participant = $this->createMock(Participant::class);
  370. $participant->expects($this->any())
  371. ->method('getAttendee')
  372. ->willReturn($attendee);
  373. $participant->expects($this->any())
  374. ->method('getPermissions')
  375. ->willReturn(Attendee::PERMISSIONS_MAX_CUSTOM);
  376. $this->participantService->expects($this->once())
  377. ->method('getParticipant')
  378. ->with($room, $this->userId)
  379. ->willReturn($participant);
  380. $room->expects($this->once())
  381. ->method('getToken')
  382. ->willReturn($roomToken);
  383. $room->expects($this->once())
  384. ->method('getPropertiesForSignaling')
  385. ->with($this->userId)
  386. ->willReturn([
  387. 'name' => $roomName,
  388. 'type' => Room::TYPE_ONE_TO_ONE,
  389. ]);
  390. $result = $this->performBackendRequest([
  391. 'type' => 'room',
  392. 'room' => [
  393. 'roomid' => $roomToken,
  394. 'userid' => $this->userId,
  395. 'sessionid' => '',
  396. ],
  397. ]);
  398. $this->assertSame([
  399. 'type' => 'room',
  400. 'room' => [
  401. 'version' => '1.0',
  402. 'roomid' => $roomToken,
  403. 'properties' => [
  404. 'name' => $roomName,
  405. 'type' => Room::TYPE_ONE_TO_ONE,
  406. ],
  407. 'permissions' => [
  408. 'publish-audio',
  409. 'publish-video',
  410. 'publish-screen',
  411. ],
  412. ],
  413. ], $result->getData());
  414. }
  415. public function testBackendRoomUserPublic(): void {
  416. $roomToken = 'the-room';
  417. $roomName = 'the-room-name';
  418. $room = $this->createMock(Room::class);
  419. $this->manager->expects($this->once())
  420. ->method('getRoomByToken')
  421. ->with($roomToken)
  422. ->willReturn($room);
  423. $attendee = Attendee::fromRow([
  424. 'permissions' => Attendee::PERMISSIONS_DEFAULT,
  425. 'actor_type' => Attendee::ACTOR_USERS,
  426. ]);
  427. $participant = $this->createMock(Participant::class);
  428. $participant->expects($this->any())
  429. ->method('getAttendee')
  430. ->willReturn($attendee);
  431. $participant->expects($this->any())
  432. ->method('getPermissions')
  433. ->willReturn(Attendee::PERMISSIONS_MAX_CUSTOM);
  434. $this->participantService->expects($this->once())
  435. ->method('getParticipant')
  436. ->with($room, $this->userId)
  437. ->willReturn($participant);
  438. $room->expects($this->once())
  439. ->method('getToken')
  440. ->willReturn($roomToken);
  441. $room->expects($this->once())
  442. ->method('getPropertiesForSignaling')
  443. ->with($this->userId)
  444. ->willReturn([
  445. 'name' => $roomName,
  446. 'type' => Room::TYPE_PUBLIC,
  447. ]);
  448. $result = $this->performBackendRequest([
  449. 'type' => 'room',
  450. 'room' => [
  451. 'roomid' => $roomToken,
  452. 'userid' => $this->userId,
  453. 'sessionid' => '',
  454. ],
  455. ]);
  456. $this->assertSame([
  457. 'type' => 'room',
  458. 'room' => [
  459. 'version' => '1.0',
  460. 'roomid' => $roomToken,
  461. 'properties' => [
  462. 'name' => $roomName,
  463. 'type' => Room::TYPE_PUBLIC,
  464. ],
  465. 'permissions' => [
  466. 'publish-audio',
  467. 'publish-video',
  468. 'publish-screen',
  469. ],
  470. ],
  471. ], $result->getData());
  472. }
  473. public function testBackendRoomModeratorPublic(): void {
  474. $roomToken = 'the-room';
  475. $roomName = 'the-room-name';
  476. $room = $this->createMock(Room::class);
  477. $this->manager->expects($this->once())
  478. ->method('getRoomByToken')
  479. ->with($roomToken)
  480. ->willReturn($room);
  481. $attendee = Attendee::fromRow([
  482. 'permissions' => Attendee::PERMISSIONS_DEFAULT,
  483. 'actor_type' => Attendee::ACTOR_USERS,
  484. ]);
  485. $participant = $this->createMock(Participant::class);
  486. $participant->expects($this->any())
  487. ->method('getAttendee')
  488. ->willReturn($attendee);
  489. $participant->expects($this->any())
  490. ->method('getPermissions')
  491. ->willReturn(Attendee::PERMISSIONS_MAX_CUSTOM);
  492. $participant->expects($this->once())
  493. ->method('hasModeratorPermissions')
  494. ->with(false)
  495. ->willReturn(true);
  496. $this->participantService->expects($this->once())
  497. ->method('getParticipant')
  498. ->with($room, $this->userId)
  499. ->willReturn($participant);
  500. $room->expects($this->once())
  501. ->method('getToken')
  502. ->willReturn($roomToken);
  503. $room->expects($this->once())
  504. ->method('getPropertiesForSignaling')
  505. ->with($this->userId)
  506. ->willReturn([
  507. 'name' => $roomName,
  508. 'type' => Room::TYPE_PUBLIC,
  509. ]);
  510. $result = $this->performBackendRequest([
  511. 'type' => 'room',
  512. 'room' => [
  513. 'roomid' => $roomToken,
  514. 'userid' => $this->userId,
  515. 'sessionid' => '',
  516. ],
  517. ]);
  518. $this->assertSame([
  519. 'type' => 'room',
  520. 'room' => [
  521. 'version' => '1.0',
  522. 'roomid' => $roomToken,
  523. 'properties' => [
  524. 'name' => $roomName,
  525. 'type' => Room::TYPE_PUBLIC,
  526. ],
  527. 'permissions' => [
  528. 'publish-audio',
  529. 'publish-video',
  530. 'publish-screen',
  531. 'control',
  532. ],
  533. ],
  534. ], $result->getData());
  535. }
  536. public function testBackendRoomAnonymousPublic(): void {
  537. $roomToken = 'the-room';
  538. $roomName = 'the-room-name';
  539. $sessionId = 'the-session';
  540. $room = $this->createMock(Room::class);
  541. $this->manager->expects($this->once())
  542. ->method('getRoomByToken')
  543. ->with($roomToken)
  544. ->willReturn($room);
  545. $attendee = Attendee::fromRow([
  546. 'permissions' => Attendee::PERMISSIONS_DEFAULT,
  547. 'actor_type' => Attendee::ACTOR_USERS,
  548. ]);
  549. $participant = $this->createMock(Participant::class);
  550. $participant->expects($this->any())
  551. ->method('getAttendee')
  552. ->willReturn($attendee);
  553. $participant->expects($this->any())
  554. ->method('getPermissions')
  555. ->willReturn(Attendee::PERMISSIONS_MAX_CUSTOM);
  556. $this->participantService->expects($this->once())
  557. ->method('getParticipantBySession')
  558. ->with($room, $sessionId)
  559. ->willReturn($participant);
  560. $room->expects($this->once())
  561. ->method('getToken')
  562. ->willReturn($roomToken);
  563. $room->expects($this->once())
  564. ->method('getPropertiesForSignaling')
  565. ->with('')
  566. ->willReturn([
  567. 'name' => $roomName,
  568. 'type' => Room::TYPE_PUBLIC,
  569. ]);
  570. $result = $this->performBackendRequest([
  571. 'type' => 'room',
  572. 'room' => [
  573. 'roomid' => $roomToken,
  574. 'userid' => '',
  575. 'sessionid' => $sessionId,
  576. ],
  577. ]);
  578. $this->assertSame([
  579. 'type' => 'room',
  580. 'room' => [
  581. 'version' => '1.0',
  582. 'roomid' => $roomToken,
  583. 'properties' => [
  584. 'name' => $roomName,
  585. 'type' => Room::TYPE_PUBLIC,
  586. ],
  587. 'permissions' => [
  588. 'publish-audio',
  589. 'publish-video',
  590. 'publish-screen',
  591. ],
  592. ],
  593. ], $result->getData());
  594. }
  595. public function testBackendRoomInvitedPublic(): void {
  596. $roomToken = 'the-room';
  597. $roomName = 'the-room-name';
  598. $sessionId = 'the-session';
  599. $room = $this->createMock(Room::class);
  600. $this->manager->expects($this->once())
  601. ->method('getRoomByToken')
  602. ->with($roomToken)
  603. ->willReturn($room);
  604. $attendee = Attendee::fromRow([
  605. 'permissions' => Attendee::PERMISSIONS_DEFAULT,
  606. 'actor_type' => Attendee::ACTOR_USERS,
  607. ]);
  608. $participant = $this->createMock(Participant::class);
  609. $participant->expects($this->any())
  610. ->method('getAttendee')
  611. ->willReturn($attendee);
  612. $participant->expects($this->any())
  613. ->method('getPermissions')
  614. ->willReturn(Attendee::PERMISSIONS_MAX_CUSTOM);
  615. $this->participantService->expects($this->once())
  616. ->method('getParticipantBySession')
  617. ->with($room, $sessionId)
  618. ->willReturn($participant);
  619. $room->expects($this->once())
  620. ->method('getToken')
  621. ->willReturn($roomToken);
  622. $room->expects($this->once())
  623. ->method('getPropertiesForSignaling')
  624. ->with($this->userId)
  625. ->willReturn([
  626. 'name' => $roomName,
  627. 'type' => Room::TYPE_PUBLIC,
  628. ]);
  629. $result = $this->performBackendRequest([
  630. 'type' => 'room',
  631. 'room' => [
  632. 'roomid' => $roomToken,
  633. 'userid' => $this->userId,
  634. 'sessionid' => $sessionId,
  635. ],
  636. ]);
  637. $this->assertSame([
  638. 'type' => 'room',
  639. 'room' => [
  640. 'version' => '1.0',
  641. 'roomid' => $roomToken,
  642. 'properties' => [
  643. 'name' => $roomName,
  644. 'type' => Room::TYPE_PUBLIC,
  645. ],
  646. 'permissions' => [
  647. 'publish-audio',
  648. 'publish-video',
  649. 'publish-screen',
  650. ],
  651. ],
  652. ], $result->getData());
  653. }
  654. public static function dataBackendRoomUserPublicPermissions(): array {
  655. return [
  656. [Attendee::PERMISSIONS_DEFAULT, []],
  657. [Attendee::PERMISSIONS_PUBLISH_AUDIO, ['publish-audio']],
  658. [Attendee::PERMISSIONS_PUBLISH_VIDEO, ['publish-video']],
  659. [Attendee::PERMISSIONS_PUBLISH_AUDIO | Attendee::PERMISSIONS_PUBLISH_VIDEO, ['publish-audio', 'publish-video']],
  660. [Attendee::PERMISSIONS_PUBLISH_SCREEN, ['publish-screen']],
  661. [Attendee::PERMISSIONS_PUBLISH_AUDIO | Attendee::PERMISSIONS_PUBLISH_SCREEN, ['publish-audio', 'publish-screen']],
  662. [Attendee::PERMISSIONS_PUBLISH_VIDEO | Attendee::PERMISSIONS_PUBLISH_SCREEN, ['publish-video', 'publish-screen']],
  663. [Attendee::PERMISSIONS_PUBLISH_AUDIO | Attendee::PERMISSIONS_PUBLISH_VIDEO | Attendee::PERMISSIONS_PUBLISH_SCREEN, ['publish-audio', 'publish-video', 'publish-screen']],
  664. ];
  665. }
  666. /**
  667. * @dataProvider dataBackendRoomUserPublicPermissions
  668. */
  669. public function testBackendRoomUserPublicPermissions(int $permissions, array $expectedBackendPermissions): void {
  670. $roomToken = 'the-room';
  671. $roomName = 'the-room-name';
  672. $room = $this->createMock(Room::class);
  673. $this->manager->expects($this->once())
  674. ->method('getRoomByToken')
  675. ->with($roomToken)
  676. ->willReturn($room);
  677. $attendee = Attendee::fromRow([
  678. 'permissions' => $permissions,
  679. 'actor_type' => Attendee::ACTOR_USERS,
  680. ]);
  681. $participant = $this->createMock(Participant::class);
  682. $participant->expects($this->any())
  683. ->method('getAttendee')
  684. ->willReturn($attendee);
  685. $participant->expects($this->any())
  686. ->method('getPermissions')
  687. ->willReturn($permissions);
  688. $this->participantService->expects($this->once())
  689. ->method('getParticipant')
  690. ->with($room, $this->userId)
  691. ->willReturn($participant);
  692. $room->expects($this->once())
  693. ->method('getToken')
  694. ->willReturn($roomToken);
  695. $room->expects($this->once())
  696. ->method('getPropertiesForSignaling')
  697. ->with($this->userId)
  698. ->willReturn([
  699. 'name' => $roomName,
  700. 'type' => Room::TYPE_PUBLIC,
  701. ]);
  702. $result = $this->performBackendRequest([
  703. 'type' => 'room',
  704. 'room' => [
  705. 'roomid' => $roomToken,
  706. 'userid' => $this->userId,
  707. 'sessionid' => '',
  708. ],
  709. ]);
  710. $this->assertSame([
  711. 'type' => 'room',
  712. 'room' => [
  713. 'version' => '1.0',
  714. 'roomid' => $roomToken,
  715. 'properties' => [
  716. 'name' => $roomName,
  717. 'type' => Room::TYPE_PUBLIC,
  718. ],
  719. 'permissions' => $expectedBackendPermissions,
  720. ],
  721. ], $result->getData());
  722. }
  723. public function testBackendRoomAnonymousOneToOne(): void {
  724. $roomToken = 'the-room';
  725. $sessionId = 'the-session';
  726. $room = $this->createMock(Room::class);
  727. $this->manager->expects($this->once())
  728. ->method('getRoomByToken')
  729. ->with($roomToken)
  730. ->willReturn($room);
  731. $this->participantService->expects($this->once())
  732. ->method('getParticipantBySession')
  733. ->willThrowException(new ParticipantNotFoundException());
  734. $result = $this->performBackendRequest([
  735. 'type' => 'room',
  736. 'room' => [
  737. 'roomid' => $roomToken,
  738. 'userid' => '',
  739. 'sessionid' => $sessionId,
  740. ],
  741. ]);
  742. $this->assertSame([
  743. 'type' => 'error',
  744. 'error' => [
  745. 'code' => 'no_such_room',
  746. 'message' => 'The user is not invited to this room.',
  747. ],
  748. ], $result->getData());
  749. }
  750. public function testBackendRoomSessionFromEvent(): void {
  751. $this->dispatcher->addListener(BeforeSignalingResponseSentEvent::class, static function (BeforeSignalingResponseSentEvent $event) {
  752. $room = $event->getRoom();
  753. $event->setSession([
  754. 'foo' => 'bar',
  755. 'room' => $room->getToken(),
  756. ]);
  757. });
  758. $roomToken = 'the-room';
  759. $roomName = 'the-room-name';
  760. $room = $this->createMock(Room::class);
  761. $this->manager->expects($this->once())
  762. ->method('getRoomByToken')
  763. ->with($roomToken)
  764. ->willReturn($room);
  765. $attendee = Attendee::fromRow([
  766. 'permissions' => Attendee::PERMISSIONS_DEFAULT,
  767. 'actor_type' => Attendee::ACTOR_USERS,
  768. ]);
  769. $participant = $this->createMock(Participant::class);
  770. $participant->expects($this->any())
  771. ->method('getAttendee')
  772. ->willReturn($attendee);
  773. $participant->expects($this->any())
  774. ->method('getPermissions')
  775. ->willReturn(Attendee::PERMISSIONS_MAX_CUSTOM);
  776. $this->participantService->expects($this->once())
  777. ->method('getParticipant')
  778. ->with($room, $this->userId)
  779. ->willReturn($participant);
  780. $room->expects($this->atLeastOnce())
  781. ->method('getToken')
  782. ->willReturn($roomToken);
  783. $room->expects($this->once())
  784. ->method('getPropertiesForSignaling')
  785. ->with($this->userId)
  786. ->willReturn([
  787. 'name' => $roomName,
  788. 'type' => Room::TYPE_ONE_TO_ONE,
  789. ]);
  790. $result = $this->performBackendRequest([
  791. 'type' => 'room',
  792. 'room' => [
  793. 'roomid' => $roomToken,
  794. 'userid' => $this->userId,
  795. 'sessionid' => '',
  796. ],
  797. ]);
  798. $this->assertSame([
  799. 'type' => 'room',
  800. 'room' => [
  801. 'version' => '1.0',
  802. 'roomid' => $roomToken,
  803. 'properties' => [
  804. 'name' => $roomName,
  805. 'type' => Room::TYPE_ONE_TO_ONE,
  806. ],
  807. 'permissions' => [
  808. 'publish-audio',
  809. 'publish-video',
  810. 'publish-screen',
  811. ],
  812. 'session' => [
  813. 'foo' => 'bar',
  814. 'room' => $roomToken,
  815. ],
  816. ],
  817. ], $result->getData());
  818. }
  819. public function testBackendPingUser(): void {
  820. $sessionId = 'the-session';
  821. $this->timeFactory->method('getTime')
  822. ->willReturn(123456);
  823. $this->sessionService->expects($this->once())
  824. ->method('updateMultipleLastPings')
  825. ->with([$sessionId], 123456);
  826. $result = $this->performBackendRequest([
  827. 'type' => 'ping',
  828. 'ping' => [
  829. 'entries' => [
  830. [
  831. 'userid' => $this->userId,
  832. 'sessionid' => $sessionId,
  833. ],
  834. ],
  835. ],
  836. ]);
  837. $this->assertSame([
  838. 'type' => 'room',
  839. 'room' => [
  840. 'version' => '1.0',
  841. ],
  842. ], $result->getData());
  843. }
  844. public function testBackendPingAnonymous(): void {
  845. $sessionId = 'the-session';
  846. $this->timeFactory->method('getTime')
  847. ->willReturn(1234567);
  848. $this->sessionService->expects($this->once())
  849. ->method('updateMultipleLastPings')
  850. ->with([$sessionId], 1234567);
  851. $result = $this->performBackendRequest([
  852. 'type' => 'ping',
  853. 'ping' => [
  854. 'entries' => [
  855. [
  856. 'userid' => '',
  857. 'sessionid' => $sessionId,
  858. ],
  859. ],
  860. ],
  861. ]);
  862. $this->assertSame([
  863. 'type' => 'room',
  864. 'room' => [
  865. 'version' => '1.0',
  866. ],
  867. ], $result->getData());
  868. }
  869. public function testBackendPingMixedAndInactive(): void {
  870. $sessionId = 'the-session';
  871. $this->timeFactory->method('getTime')
  872. ->willReturn(234567);
  873. $this->sessionService->expects($this->once())
  874. ->method('updateMultipleLastPings')
  875. ->with([$sessionId . '1', $sessionId . '2'], 234567);
  876. $result = $this->performBackendRequest([
  877. 'type' => 'ping',
  878. 'ping' => [
  879. 'entries' => [
  880. [
  881. 'userid' => '',
  882. 'sessionid' => $sessionId . '1',
  883. ],
  884. [
  885. 'userid' => $this->userId,
  886. 'sessionid' => $sessionId . '2',
  887. ],
  888. [
  889. 'userid' => 'inactive',
  890. 'sessionid' => '0',
  891. ],
  892. ],
  893. ],
  894. ]);
  895. $this->assertSame([
  896. 'type' => 'room',
  897. 'room' => [
  898. 'version' => '1.0',
  899. ],
  900. ], $result->getData());
  901. }
  902. public function testLeaveRoomWithOldSession(): void {
  903. // Make sure that leaving a user with an old session id doesn't remove
  904. // the current user from the room if he re-joined in the meantime.
  905. $dbConnection = \OCP\Server::get(IDBConnection::class);
  906. $dispatcher = \OCP\Server::get(IEventDispatcher::class);
  907. /** @var ParticipantService $participantService */
  908. $participantService = \OCP\Server::get(ParticipantService::class);
  909. $this->manager = new Manager(
  910. $dbConnection,
  911. \OCP\Server::get(IConfig::class),
  912. $this->createMock(Config::class),
  913. \OCP\Server::get(IAppManager::class),
  914. \OCP\Server::get(AttendeeMapper::class),
  915. \OCP\Server::get(SessionMapper::class),
  916. $participantService,
  917. $this->secureRandom,
  918. $this->createMock(IUserManager::class),
  919. $this->createMock(IGroupManager::class),
  920. $this->createMock(CommentsManager::class),
  921. $this->createMock(TalkSession::class),
  922. $dispatcher,
  923. $this->timeFactory,
  924. $this->createMock(IHasher::class),
  925. $this->createMock(IL10N::class),
  926. $this->createMock(Authenticator::class),
  927. );
  928. $this->recreateSignalingController();
  929. $testUser = $this->createMock(IUser::class);
  930. $testUser->expects($this->any())
  931. ->method('getDisplayName')
  932. ->willReturn('Test User');
  933. $testUser->expects($this->any())
  934. ->method('getUID')
  935. ->willReturn($this->userId);
  936. $room = $this->manager->createRoom(Room::TYPE_PUBLIC);
  937. $roomService = $this->createMock(RoomService::class);
  938. $roomService->method('verifyPassword')
  939. ->willReturn(['result' => true, 'url' => '']);
  940. // The user joined the room.
  941. $oldParticipant = $participantService->joinRoom($roomService, $room, $testUser, '');
  942. $oldSessionId = $oldParticipant->getSession()->getSessionId();
  943. $this->performBackendRequest([
  944. 'type' => 'room',
  945. 'room' => [
  946. 'roomid' => $room->getToken(),
  947. 'userid' => $this->userId,
  948. 'sessionid' => $oldSessionId,
  949. 'action' => 'join',
  950. ],
  951. ]);
  952. $participant = $participantService->getParticipant($room, $this->userId, $oldSessionId);
  953. $this->assertEquals($oldSessionId, $participant->getSession()->getSessionId());
  954. // The user is reloading the browser which will join him with another
  955. // session id.
  956. $newParticipant = $participantService->joinRoom($roomService, $room, $testUser, '');
  957. $newSessionId = $newParticipant->getSession()->getSessionId();
  958. $this->performBackendRequest([
  959. 'type' => 'room',
  960. 'room' => [
  961. 'roomid' => $room->getToken(),
  962. 'userid' => $this->userId,
  963. 'sessionid' => $newSessionId,
  964. 'action' => 'join',
  965. ],
  966. ]);
  967. // Now the new session id is stored in the database.
  968. $participant = $participantService->getParticipant($room, $this->userId, $newSessionId);
  969. $this->assertEquals($newSessionId, $participant->getSession()->getSessionId());
  970. // Leaving the old session id...
  971. $this->performBackendRequest([
  972. 'type' => 'room',
  973. 'room' => [
  974. 'roomid' => $room->getToken(),
  975. 'userid' => $this->userId,
  976. 'sessionid' => $oldSessionId,
  977. 'action' => 'leave',
  978. ],
  979. ]);
  980. // ...will keep the new session id in the database.
  981. $participant = $participantService->getParticipant($room, $this->userId, $newSessionId);
  982. $this->assertEquals($newSessionId, $participant->getSession()->getSessionId());
  983. }
  984. }