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.

2239 lines
72 KiB

  1. <?php
  2. /**
  3. * @author Joas Schilling <coding@schilljs.com>
  4. * @author Thomas Müller <thomas.mueller@tmit.eu>
  5. *
  6. * @copyright Copyright (c) 2016, 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. require __DIR__ . '/../../vendor/autoload.php';
  23. use Behat\Behat\Context\Context;
  24. use Behat\Behat\Context\SnippetAcceptingContext;
  25. use Behat\Behat\Hook\Scope\BeforeScenarioScope;
  26. use Behat\Gherkin\Node\TableNode;
  27. use GuzzleHttp\Client;
  28. use GuzzleHttp\Cookie\CookieJar;
  29. use GuzzleHttp\Exception\ClientException;
  30. use PHPUnit\Framework\Assert;
  31. use Psr\Http\Message\ResponseInterface;
  32. /**
  33. * Defines application features from the specific context.
  34. */
  35. class FeatureContext implements Context, SnippetAcceptingContext {
  36. public const TEST_PASSWORD = '123456';
  37. /** @var array[] */
  38. protected static $identifierToToken;
  39. /** @var array[] */
  40. protected static $tokenToIdentifier;
  41. /** @var array[] */
  42. protected static $sessionIdToUser;
  43. /** @var array[] */
  44. protected static $userToSessionId;
  45. /** @var array[] */
  46. protected static $userToAttendeeId;
  47. /** @var array[] */
  48. protected static $messages;
  49. protected static $permissionsMap = [
  50. 'D' => 0, // PERMISSIONS_DEFAULT
  51. 'C' => 1, // PERMISSIONS_CUSTOM
  52. 'S' => 2, // PERMISSIONS_CALL_START
  53. 'J' => 4, // PERMISSIONS_CALL_JOIN
  54. 'L' => 8, // PERMISSIONS_LOBBY_IGNORE
  55. 'A' => 16, // PERMISSIONS_PUBLISH_AUDIO
  56. 'V' => 32, // PERMISSIONS_PUBLISH_VIDEO
  57. 'P' => 64, // PERMISSIONS_PUBLISH_SCREEN
  58. ];
  59. /** @var string */
  60. protected $currentUser;
  61. /** @var ResponseInterface */
  62. private $response;
  63. /** @var CookieJar[] */
  64. private $cookieJars;
  65. /** @var string */
  66. protected $baseUrl;
  67. /** @var string */
  68. protected $lastEtag;
  69. /** @var array */
  70. protected $createdUsers = [];
  71. /** @var array */
  72. protected $createdGroups = [];
  73. /** @var array */
  74. protected $createdGuestAccountUsers = [];
  75. /** @var array */
  76. protected $changedConfigs = [];
  77. /** @var SharingContext */
  78. private $sharingContext;
  79. /** @var null|bool */
  80. private $guestsAppWasEnabled = null;
  81. /** @var string */
  82. private $guestsOldWhitelist;
  83. use CommandLineTrait;
  84. public static function getTokenForIdentifier(string $identifier) {
  85. return self::$identifierToToken[$identifier];
  86. }
  87. public function getAttendeeId(string $type, string $id, string $room, string $user = null) {
  88. if (!isset(self::$userToAttendeeId[$type][$id])) {
  89. if ($user !== null) {
  90. $this->userLoadsAttendeeIdsInRoom($user, $room, 'v4');
  91. } else {
  92. throw new \Exception('Attendee id unknown, please call userLoadsAttendeeIdsInRoom with a user that has access before');
  93. }
  94. }
  95. return self::$userToAttendeeId[$type][$id];
  96. }
  97. /**
  98. * FeatureContext constructor.
  99. */
  100. public function __construct() {
  101. $this->cookieJars = [];
  102. $this->baseUrl = getenv('TEST_SERVER_URL');
  103. $this->guestsAppWasEnabled = null;
  104. }
  105. /**
  106. * @BeforeScenario
  107. */
  108. public function setUp() {
  109. self::$identifierToToken = [];
  110. self::$tokenToIdentifier = [];
  111. self::$sessionIdToUser = [];
  112. self::$userToSessionId = [];
  113. self::$userToAttendeeId = [];
  114. self::$messages = [];
  115. $this->createdUsers = [];
  116. $this->createdGroups = [];
  117. $this->createdGuestAccountUsers = [];
  118. }
  119. /**
  120. * @BeforeScenario
  121. */
  122. public function getOtherRequiredSiblingContexts(BeforeScenarioScope $scope) {
  123. $environment = $scope->getEnvironment();
  124. $this->sharingContext = $environment->getContext("SharingContext");
  125. }
  126. /**
  127. * @AfterScenario
  128. */
  129. public function tearDown() {
  130. foreach ($this->createdUsers as $user) {
  131. $this->deleteUser($user);
  132. }
  133. foreach ($this->createdGroups as $group) {
  134. $this->deleteGroup($group);
  135. }
  136. foreach ($this->createdGuestAccountUsers as $user) {
  137. $this->deleteUser($user);
  138. }
  139. }
  140. /**
  141. * @Then /^user "([^"]*)" cannot find any listed rooms \((v4)\)$/
  142. *
  143. * @param string $user
  144. * @param string $apiVersion
  145. */
  146. public function userCannotFindAnyListedRooms(string $user, string $apiVersion): void {
  147. $this->userCanFindListedRoomsWithTerm($user, '', $apiVersion, null);
  148. }
  149. /**
  150. * @Then /^user "([^"]*)" cannot find any listed rooms with (\d+) \((v4)\)$/
  151. *
  152. * @param string $user
  153. * @param int $statusCode
  154. * @param string $apiVersion
  155. */
  156. public function userCannotFindAnyListedRoomsWithStatus(string $user, int $statusCode, string $apiVersion): void {
  157. $this->setCurrentUser($user);
  158. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/listed-room');
  159. $this->assertStatusCode($this->response, $statusCode);
  160. }
  161. /**
  162. * @Then /^user "([^"]*)" cannot find any listed rooms with term "([^"]*)" \((v4)\)$/
  163. *
  164. * @param string $user
  165. * @param string $term
  166. * @param string $apiVersion
  167. */
  168. public function userCannotFindAnyListedRoomsWithTerm(string $user, string $term, string $apiVersion): void {
  169. $this->userCanFindListedRoomsWithTerm($user, $term, $apiVersion, null);
  170. }
  171. /**
  172. * @Then /^user "([^"]*)" can find listed rooms \((v4)\)$/
  173. *
  174. * @param string $user
  175. * @param string $apiVersion
  176. * @param TableNode|null $formData
  177. */
  178. public function userCanFindListedRooms(string $user, string $apiVersion, TableNode $formData = null): void {
  179. $this->userCanFindListedRoomsWithTerm($user, '', $apiVersion, $formData);
  180. }
  181. /**
  182. * @Then /^user "([^"]*)" can find listed rooms with term "([^"]*)" \((v4)\)$/
  183. *
  184. * @param string $user
  185. * @param string $term
  186. * @param string $apiVersion
  187. * @param TableNode|null $formData
  188. */
  189. public function userCanFindListedRoomsWithTerm(string $user, string $term, string $apiVersion, TableNode $formData = null): void {
  190. $this->setCurrentUser($user);
  191. $suffix = '';
  192. if ($term !== '') {
  193. $suffix = '?searchTerm=' . \rawurlencode($term);
  194. }
  195. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/listed-room' . $suffix);
  196. $this->assertStatusCode($this->response, 200);
  197. $rooms = $this->getDataFromResponse($this->response);
  198. if ($formData === null) {
  199. Assert::assertEmpty($rooms);
  200. return;
  201. }
  202. $this->assertRooms($rooms, $formData);
  203. }
  204. /**
  205. * @Then /^user "([^"]*)" is participant of the following rooms \((v4)\)$/
  206. *
  207. * @param string $user
  208. * @param string $apiVersion
  209. * @param TableNode|null $formData
  210. */
  211. public function userIsParticipantOfRooms(string $user, string $apiVersion, TableNode $formData = null): void {
  212. $this->setCurrentUser($user);
  213. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room');
  214. $this->assertStatusCode($this->response, 200);
  215. $rooms = $this->getDataFromResponse($this->response);
  216. $rooms = array_filter($rooms, function ($room) {
  217. return $room['type'] !== 4;
  218. });
  219. if ($formData === null) {
  220. Assert::assertEmpty($rooms);
  221. return;
  222. }
  223. $this->assertRooms($rooms, $formData);
  224. }
  225. /**
  226. * @param array $rooms
  227. * @param TableNode $formData
  228. */
  229. private function assertRooms($rooms, TableNode $formData) {
  230. Assert::assertCount(count($formData->getHash()), $rooms, 'Room count does not match');
  231. Assert::assertEquals($formData->getHash(), array_map(function ($room, $expectedRoom) {
  232. if (!isset(self::$identifierToToken[$room['name']])) {
  233. self::$identifierToToken[$room['name']] = $room['token'];
  234. }
  235. if (!isset(self::$tokenToIdentifier[$room['token']])) {
  236. self::$tokenToIdentifier[$room['token']] = $room['name'];
  237. }
  238. $data = [];
  239. if (isset($expectedRoom['id'])) {
  240. $data['id'] = self::$tokenToIdentifier[$room['token']];
  241. }
  242. if (isset($expectedRoom['name'])) {
  243. $data['name'] = $room['name'];
  244. }
  245. if (isset($expectedRoom['description'])) {
  246. $data['description'] = $room['description'];
  247. }
  248. if (isset($expectedRoom['type'])) {
  249. $data['type'] = (string) $room['type'];
  250. }
  251. if (isset($expectedRoom['hasPassword'])) {
  252. $data['hasPassword'] = (string) $room['hasPassword'];
  253. }
  254. if (isset($expectedRoom['readOnly'])) {
  255. $data['readOnly'] = (string) $room['readOnly'];
  256. }
  257. if (isset($expectedRoom['listable'])) {
  258. $data['listable'] = (string) $room['listable'];
  259. }
  260. if (isset($expectedRoom['participantType'])) {
  261. $data['participantType'] = (string) $room['participantType'];
  262. }
  263. if (isset($expectedRoom['sipEnabled'])) {
  264. $data['sipEnabled'] = (string) $room['sipEnabled'];
  265. }
  266. if (isset($expectedRoom['callFlag'])) {
  267. $data['callFlag'] = (int) $room['callFlag'];
  268. }
  269. if (isset($expectedRoom['attendeePin'])) {
  270. $data['attendeePin'] = $room['attendeePin'] ? '**PIN**' : '';
  271. }
  272. if (isset($expectedRoom['lastMessage'])) {
  273. $data['lastMessage'] = $room['lastMessage'] ? $room['lastMessage']['message'] : '';
  274. }
  275. if (isset($expectedRoom['unreadMessages'])) {
  276. $data['unreadMessages'] = (int) $room['unreadMessages'];
  277. }
  278. if (isset($expectedRoom['unreadMention'])) {
  279. $data['unreadMention'] = (int) $room['unreadMention'];
  280. }
  281. if (isset($expectedRoom['unreadMentionDirect'])) {
  282. $data['unreadMentionDirect'] = (int) $room['unreadMentionDirect'];
  283. }
  284. if (isset($expectedRoom['participants'])) {
  285. throw new \Exception('participants key needs to be checked via participants endpoint');
  286. }
  287. return $data;
  288. }, $rooms, $formData->getHash()));
  289. }
  290. /**
  291. * @Then /^user "([^"]*)" (is|is not) participant of room "([^"]*)" \((v4)\)$/
  292. *
  293. * @param string $user
  294. * @param string $isOrNotParticipant
  295. * @param string $identifier
  296. * @param string $apiVersion
  297. * @param TableNode|null $formData
  298. */
  299. public function userIsParticipantOfRoom(string $user, string $isOrNotParticipant, string $identifier, string $apiVersion, TableNode $formData = null): void {
  300. if (strpos($user, 'guest') === 0) {
  301. $this->guestIsParticipantOfRoom($user, $isOrNotParticipant, $identifier, $apiVersion, $formData);
  302. return;
  303. }
  304. $this->setCurrentUser($user);
  305. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room');
  306. $this->assertStatusCode($this->response, 200);
  307. $isParticipant = $isOrNotParticipant === 'is';
  308. $rooms = $this->getDataFromResponse($this->response);
  309. $rooms = array_filter($rooms, function ($room) {
  310. return $room['type'] !== 4;
  311. });
  312. if ($isParticipant) {
  313. Assert::assertNotEmpty($rooms);
  314. }
  315. foreach ($rooms as $room) {
  316. if (self::$tokenToIdentifier[$room['token']] === $identifier) {
  317. Assert::assertEquals($isParticipant, true, 'Room ' . $identifier . ' found in user´s room list');
  318. if ($formData) {
  319. $this->assertRooms([$room], $formData);
  320. }
  321. return;
  322. }
  323. }
  324. Assert::assertEquals($isParticipant, false, 'Room ' . $identifier . ' not found in user´s room list');
  325. }
  326. /**
  327. * @Then /^user "([^"]*)" sees the following attendees in room "([^"]*)" with (\d+) \((v4)\)$/
  328. *
  329. * @param string $user
  330. * @param string $identifier
  331. * @param int $statusCode
  332. * @param string $apiVersion
  333. * @param TableNode $formData
  334. */
  335. public function userSeesAttendeesInRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
  336. $this->setCurrentUser($user);
  337. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants');
  338. $this->assertStatusCode($this->response, $statusCode);
  339. if ($formData instanceof TableNode) {
  340. $attendees = $this->getDataFromResponse($this->response);
  341. $expectedKeys = array_flip($formData->getRows()[0]);
  342. $result = [];
  343. foreach ($attendees as $attendee) {
  344. $data = [];
  345. if (isset($expectedKeys['actorType'])) {
  346. $data['actorType'] = $attendee['actorType'];
  347. }
  348. if (isset($expectedKeys['actorId'])) {
  349. $data['actorId'] = $attendee['actorId'];
  350. }
  351. if (isset($expectedKeys['participantType'])) {
  352. $data['participantType'] = (string) $attendee['participantType'];
  353. }
  354. if (isset($expectedKeys['inCall'])) {
  355. $data['inCall'] = (string) $attendee['inCall'];
  356. }
  357. if (isset($expectedKeys['attendeePin'])) {
  358. $data['attendeePin'] = $attendee['attendeePin'] ? '**PIN**' : '';
  359. }
  360. if (isset($expectedKeys['permissions'])) {
  361. $data['permissions'] = (string) $attendee['permissions'];
  362. }
  363. if (!isset(self::$userToAttendeeId[$attendee['actorType']])) {
  364. self::$userToAttendeeId[$attendee['actorType']] = [];
  365. }
  366. self::$userToAttendeeId[$attendee['actorType']][$attendee['actorId']] = $attendee['attendeeId'];
  367. $result[] = $data;
  368. }
  369. $expected = array_map(function ($attendee) {
  370. if (isset($attendee['actorId']) && substr($attendee['actorId'], 0, strlen('"guest')) === '"guest') {
  371. $attendee['actorId'] = sha1(self::$userToSessionId[trim($attendee['actorId'], '"')]);
  372. }
  373. if (isset($attendee['actorId'], $attendee['actorType']) && $attendee['actorType'] === 'federated_users') {
  374. $attendee['actorId'] .= '@' . rtrim($this->baseUrl, '/');
  375. }
  376. if (isset($attendee['participantType'])) {
  377. $attendee['participantType'] = (string)$this->mapParticipantTypeTestInput($attendee['participantType']);
  378. }
  379. return $attendee;
  380. }, $formData->getHash());
  381. $result = array_map(function ($attendee) {
  382. if (isset($attendee['permissions'])) {
  383. $attendee['permissions'] = $this->mapPermissionsAPIOutput($attendee['permissions']);
  384. }
  385. return $attendee;
  386. }, $result);
  387. usort($expected, [self::class, 'sortAttendees']);
  388. usort($result, [self::class, 'sortAttendees']);
  389. Assert::assertEquals($expected, $result);
  390. } else {
  391. Assert::assertNull($formData);
  392. }
  393. }
  394. /**
  395. * @Then /^user "([^"]*)" loads attendees attendee ids in room "([^"]*)" \((v4)\)$/
  396. *
  397. * @param string $user
  398. * @param string $identifier
  399. * @param string $apiVersion
  400. */
  401. public function userLoadsAttendeeIdsInRoom(string $user, string $identifier, string $apiVersion, TableNode $formData = null): void {
  402. $this->setCurrentUser($user);
  403. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants');
  404. $this->assertStatusCode($this->response, 200);
  405. $attendees = $this->getDataFromResponse($this->response);
  406. foreach ($attendees as $attendee) {
  407. if (!isset(self::$userToAttendeeId[$attendee['actorType']])) {
  408. self::$userToAttendeeId[$attendee['actorType']] = [];
  409. }
  410. self::$userToAttendeeId[$attendee['actorType']][$attendee['actorId']] = $attendee['attendeeId'];
  411. }
  412. }
  413. protected static function sortAttendees(array $a1, array $a2): int {
  414. if (array_key_exists('participantType', $a1) && array_key_exists('participantType', $a2) && $a1['participantType'] !== $a2['participantType']) {
  415. return $a1['participantType'] <=> $a2['participantType'];
  416. }
  417. if ($a1['actorType'] !== $a2['actorType']) {
  418. return $a1['actorType'] <=> $a2['actorType'];
  419. }
  420. return $a1['actorId'] <=> $a2['actorId'];
  421. }
  422. private function mapParticipantTypeTestInput($participantType) {
  423. if (is_numeric($participantType)) {
  424. return $participantType;
  425. }
  426. switch ($participantType) {
  427. case 'OWNER': return 1;
  428. case 'MODERATOR': return 2;
  429. case 'USER': return 3;
  430. case 'GUEST': return 4;
  431. case 'USER_SELF_JOINED': return 5;
  432. case 'GUEST_MODERATOR': return 6;
  433. }
  434. Assert::fail('Invalid test input value for participant type');
  435. }
  436. private function mapPermissionsTestInput($permissions): int {
  437. if (is_numeric($permissions)) {
  438. return $permissions;
  439. }
  440. $numericPermissions = 0;
  441. foreach (self::$permissionsMap as $char => $int) {
  442. if (strpos($permissions, $char) !== false) {
  443. $numericPermissions += $int;
  444. $permissions = str_replace($char, '', $permissions);
  445. }
  446. }
  447. if (trim($permissions) !== '') {
  448. Assert::fail('Invalid test input value for permissions');
  449. }
  450. return $numericPermissions;
  451. }
  452. private function mapPermissionsAPIOutput($permissions): string {
  453. $permissions = (int) $permissions;
  454. $permissionsString = '';
  455. foreach (self::$permissionsMap as $char => $int) {
  456. if ($permissions & $int) {
  457. $permissionsString .= $char;
  458. $permissions &= ~ $int;
  459. }
  460. }
  461. if ($permissions !== 0) {
  462. Assert::fail('Invalid API output value for permissions');
  463. }
  464. return $permissionsString;
  465. }
  466. /**
  467. * @param string $guest
  468. * @param string $isOrNotParticipant
  469. * @param string $identifier
  470. * @param string $apiVersion
  471. * @param TableNode|null $formData
  472. */
  473. private function guestIsParticipantOfRoom(string $guest, string $isOrNotParticipant, string $identifier, string $apiVersion, TableNode $formData = null): void {
  474. $this->setCurrentUser($guest);
  475. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
  476. $response = $this->getDataFromResponse($this->response);
  477. $isParticipant = $isOrNotParticipant === 'is';
  478. if ($formData) {
  479. $rooms = [$response];
  480. $this->assertRooms($rooms, $formData);
  481. }
  482. if ($isParticipant) {
  483. $this->assertStatusCode($this->response, 200);
  484. Assert::assertEquals(self::$userToSessionId[$guest], $response['sessionId']);
  485. return;
  486. }
  487. if ($this->response->getStatusCode() === 200) {
  488. // Public rooms can always be got, but if the guest is not a
  489. // participant the sessionId will be 0.
  490. Assert::assertEquals(0, $response['sessionId']);
  491. return;
  492. }
  493. $this->assertStatusCode($this->response, 404);
  494. }
  495. /**
  496. * @Then /^user "([^"]*)" creates room "([^"]*)" \((v4)\)$/
  497. *
  498. * @param string $user
  499. * @param string $identifier
  500. * @param string $apiVersion
  501. * @param TableNode|null $formData
  502. */
  503. public function userCreatesRoom(string $user, string $identifier, string $apiVersion, TableNode $formData = null): void {
  504. $this->userCreatesRoomWith($user, $identifier, 201, $apiVersion, $formData);
  505. }
  506. /**
  507. * @Then /^user "([^"]*)" creates room "([^"]*)" with (\d+) \((v4)\)$/
  508. *
  509. * @param string $user
  510. * @param string $identifier
  511. * @param int $statusCode
  512. * @param string $apiVersion
  513. * @param TableNode|null $formData
  514. */
  515. public function userCreatesRoomWith(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  516. $this->setCurrentUser($user);
  517. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $formData);
  518. $this->assertStatusCode($this->response, $statusCode);
  519. $response = $this->getDataFromResponse($this->response);
  520. if ($statusCode === 201) {
  521. self::$identifierToToken[$identifier] = $response['token'];
  522. self::$tokenToIdentifier[$response['token']] = $identifier;
  523. }
  524. }
  525. /**
  526. * @Then /^user "([^"]*)" tries to create room with (\d+) \((v4)\)$/
  527. *
  528. * @param string $user
  529. * @param int $statusCode
  530. * @param string $apiVersion
  531. * @param TableNode|null $formData
  532. */
  533. public function userTriesToCreateRoom(string $user, int $statusCode, string $apiVersion = 'v1', TableNode $formData = null): void {
  534. $this->setCurrentUser($user);
  535. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/room', $formData);
  536. $this->assertStatusCode($this->response, $statusCode);
  537. }
  538. /**
  539. * @Then /^user "([^"]*)" gets the room for path "([^"]*)" with (\d+) \((v1)\)$/
  540. *
  541. * @param string $user
  542. * @param string $path
  543. * @param int $statusCode
  544. * @param string $apiVersion
  545. */
  546. public function userGetsTheRoomForPath(string $user, string $path, int $statusCode, string $apiVersion): void {
  547. $fileId = $this->getFileIdForPath($user, $path);
  548. $this->setCurrentUser($user);
  549. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/file/' . $fileId);
  550. $this->assertStatusCode($this->response, $statusCode);
  551. if ($statusCode !== 200) {
  552. return;
  553. }
  554. $response = $this->getDataFromResponse($this->response);
  555. $identifier = 'file ' . $path . ' room';
  556. self::$identifierToToken[$identifier] = $response['token'];
  557. self::$tokenToIdentifier[$response['token']] = $identifier;
  558. }
  559. /**
  560. * @param string $user
  561. * @param string $path
  562. * @return int
  563. */
  564. private function getFileIdForPath($user, $path) {
  565. $this->currentUser = $user;
  566. $url = "/$user/$path";
  567. $headers = [];
  568. $headers['Depth'] = 0;
  569. $body = '<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">' .
  570. ' <d:prop>' .
  571. ' <oc:fileid/>' .
  572. ' </d:prop>' .
  573. '</d:propfind>';
  574. $this->sendingToDav('PROPFIND', $url, $headers, $body);
  575. $this->assertStatusCode($this->response, 207);
  576. $xmlResponse = simplexml_load_string($this->response->getBody());
  577. $xmlResponse->registerXPathNamespace('oc', 'http://owncloud.org/ns');
  578. return (int)$xmlResponse->xpath('//oc:fileid')[0];
  579. }
  580. /**
  581. * @param string $verb
  582. * @param string $url
  583. * @param array $headers
  584. * @param string $body
  585. */
  586. private function sendingToDav(string $verb, string $url, array $headers = null, string $body = null) {
  587. $fullUrl = $this->baseUrl . 'remote.php/dav/files' . $url;
  588. $client = new Client();
  589. $options = [];
  590. if ($this->currentUser === 'admin') {
  591. $options['auth'] = 'admin';
  592. } else {
  593. $options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
  594. }
  595. $options['headers'] = [
  596. 'OCS_APIREQUEST' => 'true'
  597. ];
  598. if ($headers !== null) {
  599. $options['headers'] = array_merge($options['headers'], $headers);
  600. }
  601. if ($body !== null) {
  602. $options['body'] = $body;
  603. }
  604. try {
  605. $this->response = $client->{$verb}($fullUrl, $options);
  606. } catch (GuzzleHttp\Exception\ClientException $ex) {
  607. $this->response = $ex->getResponse();
  608. }
  609. }
  610. /**
  611. * @Then /^user "([^"]*)" gets the room for last share with (\d+) \((v1)\)$/
  612. *
  613. * @param string $user
  614. * @param int $statusCode
  615. * @param string $apiVersion
  616. */
  617. public function userGetsTheRoomForLastShare(string $user, int $statusCode, string $apiVersion): void {
  618. $shareToken = $this->sharingContext->getLastShareToken();
  619. $this->setCurrentUser($user);
  620. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/publicshare/' . $shareToken);
  621. $this->assertStatusCode($this->response, $statusCode);
  622. if ($statusCode !== 200) {
  623. return;
  624. }
  625. $response = $this->getDataFromResponse($this->response);
  626. $identifier = 'file last share room';
  627. self::$identifierToToken[$identifier] = $response['token'];
  628. self::$tokenToIdentifier[$response['token']] = $identifier;
  629. }
  630. /**
  631. * @Then /^user "([^"]*)" creates the password request room for last share with (\d+) \((v1)\)$/
  632. *
  633. * @param string $user
  634. * @param int $statusCode
  635. * @param string $apiVersion
  636. */
  637. public function userCreatesThePasswordRequestRoomForLastShare(string $user, int $statusCode, string $apiVersion): void {
  638. $shareToken = $this->sharingContext->getLastShareToken();
  639. $this->setCurrentUser($user);
  640. $this->sendRequest('POST', '/apps/spreed/api/' . $apiVersion . '/publicshareauth', ['shareToken' => $shareToken]);
  641. $this->assertStatusCode($this->response, $statusCode);
  642. if ($statusCode !== 201) {
  643. return;
  644. }
  645. $response = $this->getDataFromResponse($this->response);
  646. $identifier = 'password request for last share room';
  647. self::$identifierToToken[$identifier] = $response['token'];
  648. self::$tokenToIdentifier[$response['token']] = $identifier;
  649. }
  650. /**
  651. * @Then /^user "([^"]*)" joins room "([^"]*)" with (\d+) \((v4)\)$/
  652. *
  653. * @param string $user
  654. * @param string $identifier
  655. * @param int $statusCode
  656. * @param string $apiVersion
  657. * @param TableNode|null $formData
  658. */
  659. public function userJoinsRoom(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
  660. $this->setCurrentUser($user);
  661. $this->sendRequest(
  662. 'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/active',
  663. $formData
  664. );
  665. $this->assertStatusCode($this->response, $statusCode);
  666. if ($statusCode !== 200) {
  667. return;
  668. }
  669. $response = $this->getDataFromResponse($this->response);
  670. if (array_key_exists('sessionId', $response)) {
  671. // In the chat guest users are identified by their sessionId. The
  672. // sessionId is larger than the size of the actorId column in the
  673. // database, though, so the ID stored in the database and returned
  674. // in chat messages is a hashed version instead.
  675. self::$sessionIdToUser[sha1($response['sessionId'])] = $user;
  676. self::$userToSessionId[$user] = $response['sessionId'];
  677. }
  678. }
  679. /**
  680. * @Then /^user "([^"]*)" leaves room "([^"]*)" with (\d+) \((v4)\)$/
  681. *
  682. * @param string $user
  683. * @param string $identifier
  684. * @param int $statusCode
  685. * @param string $apiVersion
  686. */
  687. public function userExitsRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  688. $this->setCurrentUser($user);
  689. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/active');
  690. $this->assertStatusCode($this->response, $statusCode);
  691. }
  692. /**
  693. * @Then /^user "([^"]*)" removes themselves from room "([^"]*)" with (\d+) \((v4)\)$/
  694. *
  695. * @param string $user
  696. * @param string $identifier
  697. * @param int $statusCode
  698. * @param string $apiVersion
  699. */
  700. public function userLeavesRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  701. $this->setCurrentUser($user);
  702. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants/self');
  703. $this->assertStatusCode($this->response, $statusCode);
  704. }
  705. /**
  706. * @Then /^user "([^"]*)" removes "([^"]*)" from room "([^"]*)" with (\d+) \((v4)\)$/
  707. *
  708. * @param string $user
  709. * @param string $toRemove
  710. * @param string $identifier
  711. * @param int $statusCode
  712. * @param string $apiVersion
  713. */
  714. public function userRemovesUserFromRoom(string $user, string $toRemove, string $identifier, int $statusCode, string$apiVersion): void {
  715. if ($toRemove === 'stranger') {
  716. $attendeeId = 123456789;
  717. } else {
  718. $attendeeId = $this->getAttendeeId('users', $toRemove, $identifier, $statusCode === 200 ? $user : null);
  719. }
  720. $this->setCurrentUser($user);
  721. $this->sendRequest(
  722. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees',
  723. new TableNode([['attendeeId', $attendeeId]])
  724. );
  725. $this->assertStatusCode($this->response, $statusCode);
  726. }
  727. /**
  728. * @Then /^user "([^"]*)" removes (user|group|email) "([^"]*)" from room "([^"]*)" with (\d+) \((v4)\)$/
  729. *
  730. * @param string $user
  731. * @param string $actorType
  732. * @param string $actorId
  733. * @param string $identifier
  734. * @param int $statusCode
  735. * @param string $apiVersion
  736. */
  737. public function userRemovesAttendeeFromRoom(string $user, string $actorType, string $actorId, string $identifier, int $statusCode, string$apiVersion): void {
  738. if ($actorId === 'stranger') {
  739. $attendeeId = 123456789;
  740. } else {
  741. $attendeeId = $this->getAttendeeId($actorType . 's', $actorId, $identifier, $statusCode === 200 ? $user : null);
  742. }
  743. $this->setCurrentUser($user);
  744. $this->sendRequest(
  745. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees',
  746. new TableNode([['attendeeId', $attendeeId]])
  747. );
  748. $this->assertStatusCode($this->response, $statusCode);
  749. }
  750. /**
  751. * @Then /^user "([^"]*)" deletes room "([^"]*)" with (\d+) \((v4)\)$/
  752. *
  753. * @param string $user
  754. * @param string $identifier
  755. * @param int $statusCode
  756. * @param string $apiVersion
  757. */
  758. public function userDeletesRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  759. $this->setCurrentUser($user);
  760. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
  761. $this->assertStatusCode($this->response, $statusCode);
  762. }
  763. /**
  764. * @Then /^user "([^"]*)" gets room "([^"]*)" with (\d+) \((v4)\)$/
  765. *
  766. * @param string $user
  767. * @param string $identifier
  768. * @param int $statusCode
  769. * @param string $apiVersion
  770. */
  771. public function userGetsRoom(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  772. $this->setCurrentUser($user);
  773. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier]);
  774. $this->assertStatusCode($this->response, $statusCode);
  775. }
  776. /**
  777. * @Then /^user "([^"]*)" renames room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  778. *
  779. * @param string $user
  780. * @param string $identifier
  781. * @param string $newName
  782. * @param int $statusCode
  783. * @param string $apiVersion
  784. */
  785. public function userRenamesRoom(string $user, string $identifier, string $newName, int $statusCode, string $apiVersion): void {
  786. $this->setCurrentUser($user);
  787. $this->sendRequest(
  788. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier],
  789. new TableNode([['roomName', $newName]])
  790. );
  791. $this->assertStatusCode($this->response, $statusCode);
  792. }
  793. /**
  794. * @When /^user "([^"]*)" sets description for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  795. *
  796. * @param string $user
  797. * @param string $identifier
  798. * @param string $description
  799. * @param int $statusCode
  800. * @param string $apiVersion
  801. * @param TableNode
  802. */
  803. public function userSetsDescriptionForRoomTo(string $user, string $identifier, string $description, int $statusCode, string $apiVersion): void {
  804. $this->setCurrentUser($user);
  805. $this->sendRequest(
  806. 'PUT', '/apps/spreed/api/' .$apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/description',
  807. new TableNode([['description', $description]])
  808. );
  809. $this->assertStatusCode($this->response, $statusCode);
  810. }
  811. /**
  812. * @When /^user "([^"]*)" sets password "([^"]*)" for room "([^"]*)" with (\d+) \((v4)\)$/
  813. *
  814. * @param string $user
  815. * @param string $password
  816. * @param string $identifier
  817. * @param int $statusCode
  818. * @param string $apiVersion
  819. */
  820. public function userSetsTheRoomPassword(string $user, string $password, string $identifier, int $statusCode, string $apiVersion): void {
  821. $this->setCurrentUser($user);
  822. $this->sendRequest(
  823. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/password',
  824. new TableNode([['password', $password]])
  825. );
  826. $this->assertStatusCode($this->response, $statusCode);
  827. }
  828. /**
  829. * @When /^user "([^"]*)" sets lobby state for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  830. *
  831. * @param string $user
  832. * @param string $identifier
  833. * @param string $lobbyStateString
  834. * @param int $statusCode
  835. * @param string $apiVersion
  836. */
  837. public function userSetsLobbyStateForRoomTo(string $user, string $identifier, string $lobbyStateString, int $statusCode, string $apiVersion): void {
  838. if ($lobbyStateString === 'no lobby') {
  839. $lobbyState = 0;
  840. } elseif ($lobbyStateString === 'non moderators') {
  841. $lobbyState = 1;
  842. } else {
  843. Assert::fail('Invalid lobby state');
  844. }
  845. $this->setCurrentUser($user);
  846. $this->sendRequest(
  847. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/lobby',
  848. new TableNode([['state', $lobbyState]])
  849. );
  850. $this->assertStatusCode($this->response, $statusCode);
  851. }
  852. /**
  853. * @When /^user "([^"]*)" sets SIP state for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  854. *
  855. * @param string $user
  856. * @param string $identifier
  857. * @param string $SIPStateString
  858. * @param int $statusCode
  859. * @param string $apiVersion
  860. */
  861. public function userSetsSIPStateForRoomTo(string $user, string $identifier, string $SIPStateString, int $statusCode, string $apiVersion): void {
  862. if ($SIPStateString === 'disabled') {
  863. $SIPState = 0;
  864. } elseif ($SIPStateString === 'enabled') {
  865. $SIPState = 1;
  866. } else {
  867. Assert::fail('Invalid SIP state');
  868. }
  869. $this->setCurrentUser($user);
  870. $this->sendRequest(
  871. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/webinar/sip',
  872. new TableNode([['state', $SIPState]])
  873. );
  874. $this->assertStatusCode($this->response, $statusCode);
  875. }
  876. /**
  877. * @Then /^user "([^"]*)" makes room "([^"]*)" (public|private) with (\d+) \((v4)\)$/
  878. *
  879. * @param string $user
  880. * @param string $identifier
  881. * @param string $newType
  882. * @param int $statusCode
  883. * @param string $apiVersion
  884. */
  885. public function userChangesTypeOfTheRoom(string $user, string $identifier, string $newType, int $statusCode, string $apiVersion): void {
  886. $this->setCurrentUser($user);
  887. $this->sendRequest(
  888. $newType === 'public' ? 'POST' : 'DELETE',
  889. '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/public'
  890. );
  891. $this->assertStatusCode($this->response, $statusCode);
  892. }
  893. /**
  894. * @Then /^user "([^"]*)" (locks|unlocks) room "([^"]*)" with (\d+) \((v4)\)$/
  895. *
  896. * @param string $user
  897. * @param string $newState
  898. * @param string $identifier
  899. * @param int $statusCode
  900. * @param string $apiVersion
  901. */
  902. public function userChangesReadOnlyStateOfTheRoom(string $user, string $newState, string $identifier, int $statusCode, string $apiVersion): void {
  903. $this->setCurrentUser($user);
  904. $this->sendRequest(
  905. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/read-only',
  906. new TableNode([['state', $newState === 'unlocks' ? 0 : 1]])
  907. );
  908. $this->assertStatusCode($this->response, $statusCode);
  909. }
  910. /**
  911. * @Then /^user "([^"]*)" allows listing room "([^"]*)" for "(none|users|all|\d+)" with (\d+) \((v4)\)$/
  912. *
  913. * @param string $user
  914. * @param string $identifier
  915. * @param string|int $newState
  916. * @param int $statusCode
  917. * @param string $apiVersion
  918. */
  919. public function userChangesListableScopeOfTheRoom(string $user, string $identifier, $newState, int $statusCode, string $apiVersion): void {
  920. $this->setCurrentUser($user);
  921. if ($newState === 'none') {
  922. $newStateValue = 0; // Room::LISTABLE_NONE
  923. } elseif ($newState === 'users') {
  924. $newStateValue = 1; // Room::LISTABLE_USERS
  925. } elseif ($newState === 'all') {
  926. $newStateValue = 2; // Room::LISTABLE_ALL
  927. } elseif (is_numeric($newState)) {
  928. $newStateValue = (int)$newState;
  929. } else {
  930. Assert::fail('Invalid listable scope value');
  931. }
  932. $this->sendRequest(
  933. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/listable',
  934. new TableNode([['scope', $newStateValue]])
  935. );
  936. $this->assertStatusCode($this->response, $statusCode);
  937. }
  938. /**
  939. * @Then /^user "([^"]*)" adds (user|group|email|circle|remote) "([^"]*)" to room "([^"]*)" with (\d+) \((v4)\)$/
  940. *
  941. * @param string $user
  942. * @param string $newType
  943. * @param string $newId
  944. * @param string $identifier
  945. * @param int $statusCode
  946. * @param string $apiVersion
  947. */
  948. public function userAddAttendeeToRoom(string $user, string $newType, string $newId, string $identifier, int $statusCode, string $apiVersion): void {
  949. $this->setCurrentUser($user);
  950. if ($newType === 'remote') {
  951. $newId .= '@' . $this->baseUrl;
  952. }
  953. $this->sendRequest(
  954. 'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/participants',
  955. new TableNode([
  956. ['source', $newType . 's'],
  957. ['newParticipant', $newId],
  958. ])
  959. );
  960. $this->assertStatusCode($this->response, $statusCode);
  961. }
  962. /**
  963. * @Then /^user "([^"]*)" (promotes|demotes) "([^"]*)" in room "([^"]*)" with (\d+) \((v4)\)$/
  964. *
  965. * @param string $user
  966. * @param string $isPromotion
  967. * @param string $participant
  968. * @param string $identifier
  969. * @param int $statusCode
  970. * @param string $apiVersion
  971. */
  972. public function userPromoteDemoteInRoom(string $user, string $isPromotion, string $participant, string $identifier, int $statusCode, string $apiVersion): void {
  973. if ($participant === 'stranger') {
  974. $attendeeId = 123456789;
  975. } elseif (strpos($participant, 'guest') === 0) {
  976. $sessionId = self::$userToSessionId[$participant];
  977. $attendeeId = $this->getAttendeeId('guests', sha1($sessionId), $identifier, $statusCode === 200 ? $user : null);
  978. } else {
  979. $attendeeId = $this->getAttendeeId('users', $participant, $identifier, $statusCode === 200 ? $user : null);
  980. }
  981. $requestParameters = [['attendeeId', $attendeeId]];
  982. $this->setCurrentUser($user);
  983. $this->sendRequest(
  984. $isPromotion === 'promotes' ? 'POST' : 'DELETE',
  985. '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/moderators',
  986. new TableNode($requestParameters)
  987. );
  988. $this->assertStatusCode($this->response, $statusCode);
  989. }
  990. /**
  991. * @When /^user "([^"]*)" sets permissions for "([^"]*)" in room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  992. *
  993. * @param string $user
  994. * @param string $participant
  995. * @param string $identifier
  996. * @param string $permissionsString
  997. * @param int $statusCode
  998. * @param string $apiVersion
  999. */
  1000. public function userSetsPermissionsForInRoomTo(string $user, string $participant, string $identifier, string $permissionsString, int $statusCode, string $apiVersion): void {
  1001. if ($participant === 'stranger') {
  1002. $attendeeId = 123456789;
  1003. } elseif (strpos($participant, 'guest') === 0) {
  1004. $sessionId = self::$userToSessionId[$participant];
  1005. $attendeeId = $this->getAttendeeId('guests', sha1($sessionId), $identifier, $statusCode === 200 ? $user : null);
  1006. } else {
  1007. $attendeeId = $this->getAttendeeId('users', $participant, $identifier, $statusCode === 200 ? $user : null);
  1008. }
  1009. $permissions = $this->mapPermissionsTestInput($permissionsString);
  1010. $requestParameters = [
  1011. ['attendeeId', $attendeeId],
  1012. ['permissions', $permissions],
  1013. ['method', 'set'],
  1014. ];
  1015. $this->setCurrentUser($user);
  1016. $this->sendRequest(
  1017. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/attendees/permissions',
  1018. new TableNode($requestParameters)
  1019. );
  1020. $this->assertStatusCode($this->response, $statusCode);
  1021. }
  1022. /**
  1023. * @When /^user "([^"]*)" sets (call|default) permissions for room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  1024. *
  1025. * @param string $user
  1026. * @param string $mode
  1027. * @param string $identifier
  1028. * @param string $permissionsString
  1029. * @param int $statusCode
  1030. * @param string $apiVersion
  1031. */
  1032. public function userSetsPermissionsForRoomTo(string $user, string $mode, string $identifier, string $permissionsString, int $statusCode, string $apiVersion): void {
  1033. $permissions = $this->mapPermissionsTestInput($permissionsString);
  1034. $requestParameters = [
  1035. ['permissions', $permissions],
  1036. ];
  1037. $this->setCurrentUser($user);
  1038. $this->sendRequest(
  1039. 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/permissions/' . $mode,
  1040. new TableNode($requestParameters)
  1041. );
  1042. $this->assertStatusCode($this->response, $statusCode);
  1043. }
  1044. /**
  1045. * @Then /^user "([^"]*)" joins call "([^"]*)" with (\d+) \((v4)\)$/
  1046. *
  1047. * @param string $user
  1048. * @param string $identifier
  1049. * @param int $statusCode
  1050. * @param string $apiVersion
  1051. * @param TableNode|null $formData
  1052. */
  1053. public function userJoinsCall(string $user, string $identifier, int $statusCode, string $apiVersion, TableNode $formData = null): void {
  1054. $this->setCurrentUser($user);
  1055. $this->sendRequest(
  1056. 'POST', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
  1057. $formData
  1058. );
  1059. $this->assertStatusCode($this->response, $statusCode);
  1060. $response = $this->getDataFromResponse($this->response);
  1061. if (array_key_exists('sessionId', $response)) {
  1062. // In the chat guest users are identified by their sessionId. The
  1063. // sessionId is larger than the size of the actorId column in the
  1064. // database, though, so the ID stored in the database and returned
  1065. // in chat messages is a hashed version instead.
  1066. self::$sessionIdToUser[sha1($response['sessionId'])] = $user;
  1067. self::$userToSessionId[$user] = $response['sessionId'];
  1068. }
  1069. }
  1070. /**
  1071. * @Then /^user "([^"]*)" updates call flags in room "([^"]*)" to "([^"]*)" with (\d+) \((v4)\)$/
  1072. *
  1073. * @param string $user
  1074. * @param string $identifier
  1075. * @param string $flags
  1076. * @param int $statusCode
  1077. * @param string $apiVersion
  1078. */
  1079. public function userUpdatesCallFlagsInRoomTo(string $user, string $identifier, string $flags, int $statusCode, string $apiVersion): void {
  1080. $this->setCurrentUser($user);
  1081. $this->sendRequest(
  1082. 'PUT', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
  1083. new TableNode([['flags', $flags]])
  1084. );
  1085. $this->assertStatusCode($this->response, $statusCode);
  1086. }
  1087. /**
  1088. * @Then /^user "([^"]*)" leaves call "([^"]*)" with (\d+) \((v4)\)$/
  1089. *
  1090. * @param string $user
  1091. * @param string $identifier
  1092. * @param int $statusCode
  1093. * @param string $apiVersion
  1094. */
  1095. public function userLeavesCall(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  1096. $this->setCurrentUser($user);
  1097. $this->sendRequest('DELETE', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier]);
  1098. $this->assertStatusCode($this->response, $statusCode);
  1099. }
  1100. /**
  1101. * @Then /^user "([^"]*)" ends call "([^"]*)" with (\d+) \((v4)\)$/
  1102. *
  1103. * @param string $user
  1104. * @param string $identifier
  1105. * @param int $statusCode
  1106. * @param string $apiVersion
  1107. */
  1108. public function userEndsCall(string $user, string $identifier, int $statusCode, string $apiVersion): void {
  1109. $requestParameters = [
  1110. ['all', true],
  1111. ];
  1112. $this->setCurrentUser($user);
  1113. $this->sendRequest(
  1114. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier],
  1115. new TableNode($requestParameters)
  1116. );
  1117. $this->assertStatusCode($this->response, $statusCode);
  1118. }
  1119. /**
  1120. * @Then /^user "([^"]*)" sees (\d+) peers in call "([^"]*)" with (\d+) \((v4)\)$/
  1121. *
  1122. * @param string $user
  1123. * @param int $numPeers
  1124. * @param string $identifier
  1125. * @param int $statusCode
  1126. * @param string $apiVersion
  1127. */
  1128. public function userSeesPeersInCall(string $user, int $numPeers, string $identifier, int $statusCode, string $apiVersion): void {
  1129. $this->setCurrentUser($user);
  1130. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/call/' . self::$identifierToToken[$identifier]);
  1131. $this->assertStatusCode($this->response, $statusCode);
  1132. if ($statusCode === 200) {
  1133. $response = $this->getDataFromResponse($this->response);
  1134. Assert::assertCount((int) $numPeers, $response);
  1135. } else {
  1136. Assert::assertEquals((int) $numPeers, 0);
  1137. }
  1138. }
  1139. /**
  1140. * @Then /^user "([^"]*)" sends message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1141. *
  1142. * @param string $user
  1143. * @param string $message
  1144. * @param string $identifier
  1145. * @param string $statusCode
  1146. * @param string $apiVersion
  1147. */
  1148. public function userSendsMessageToRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
  1149. $this->setCurrentUser($user);
  1150. $this->sendRequest(
  1151. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
  1152. new TableNode([['message', $message]])
  1153. );
  1154. $this->assertStatusCode($this->response, $statusCode);
  1155. sleep(1); // make sure Postgres manages the order of the messages
  1156. $response = $this->getDataFromResponse($this->response);
  1157. if (isset($response['id'])) {
  1158. self::$messages[$message] = $response['id'];
  1159. }
  1160. }
  1161. /**
  1162. * @Then /^user "([^"]*)" shares rich-object "([^"]*)" "([^"]*)" '([^']*)' to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1163. *
  1164. * @param string $user
  1165. * @param string $type
  1166. * @param string $id
  1167. * @param string $metaData
  1168. * @param string $identifier
  1169. * @param string $statusCode
  1170. * @param string $apiVersion
  1171. */
  1172. public function userSharesRichObjectToRoom($user, $type, $id, $metaData, $identifier, $statusCode, $apiVersion = 'v1') {
  1173. $this->setCurrentUser($user);
  1174. $this->sendRequest(
  1175. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/share',
  1176. new TableNode([
  1177. ['objectType', $type],
  1178. ['objectId', $id],
  1179. ['metaData', $metaData],
  1180. ])
  1181. );
  1182. $this->assertStatusCode($this->response, $statusCode);
  1183. sleep(1); // make sure Postgres manages the order of the messages
  1184. $response = $this->getDataFromResponse($this->response);
  1185. if (isset($response['id'])) {
  1186. self::$messages['shared::' . $type . '::' . $id] = $response['id'];
  1187. }
  1188. }
  1189. /**
  1190. * @Then /^user "([^"]*)" deletes message "([^"]*)" from room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1191. *
  1192. * @param string $user
  1193. * @param string $message
  1194. * @param string $identifier
  1195. * @param string $statusCode
  1196. * @param string $apiVersion
  1197. */
  1198. public function userDeletesMessageFromRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
  1199. $this->setCurrentUser($user);
  1200. $this->sendRequest(
  1201. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/' . self::$messages[$message],
  1202. new TableNode([['message', $message]])
  1203. );
  1204. $this->assertStatusCode($this->response, $statusCode);
  1205. }
  1206. /**
  1207. * @Then /^user "([^"]*)" deletes chat history for room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1208. *
  1209. * @param string $user
  1210. * @param string $identifier
  1211. * @param int $statusCode
  1212. * @param string $apiVersion
  1213. */
  1214. public function userDeletesHistoryFromRoom(string $user, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
  1215. $this->setCurrentUser($user);
  1216. $this->sendRequest(
  1217. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier]
  1218. );
  1219. $this->assertStatusCode($this->response, $statusCode);
  1220. }
  1221. /**
  1222. * @Then /^user "([^"]*)" reads message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1223. *
  1224. * @param string $user
  1225. * @param string $message
  1226. * @param string $identifier
  1227. * @param string $statusCode
  1228. * @param string $apiVersion
  1229. */
  1230. public function userReadsMessageInRoom($user, $message, $identifier, $statusCode, $apiVersion = 'v1') {
  1231. $this->setCurrentUser($user);
  1232. $this->sendRequest(
  1233. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/read',
  1234. new TableNode([['lastReadMessage', self::$messages[$message]]])
  1235. );
  1236. $this->assertStatusCode($this->response, $statusCode);
  1237. }
  1238. /**
  1239. * @Then /^user "([^"]*)" marks room "([^"]*)" as unread with (\d+)(?: \((v1)\))?$/
  1240. *
  1241. * @param string $user
  1242. * @param string $identifier
  1243. * @param string $statusCode
  1244. * @param string $apiVersion
  1245. */
  1246. public function userMarkUnreadRoom($user, $identifier, $statusCode, $apiVersion = 'v1') {
  1247. $this->setCurrentUser($user);
  1248. $this->sendRequest(
  1249. 'DELETE', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/read',
  1250. );
  1251. $this->assertStatusCode($this->response, $statusCode);
  1252. }
  1253. /**
  1254. * @Then /^user "([^"]*)" sends message "([^"]*)" with reference id "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1255. *
  1256. * @param string $user
  1257. * @param string $message
  1258. * @param string $referenceId
  1259. * @param string $identifier
  1260. * @param string $statusCode
  1261. * @param string $apiVersion
  1262. */
  1263. public function userSendsMessageWithReferenceIdToRoom($user, $message, $referenceId, $identifier, $statusCode, $apiVersion = 'v1') {
  1264. $this->setCurrentUser($user);
  1265. $this->sendRequest(
  1266. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
  1267. new TableNode([['message', $message], ['referenceId', $referenceId]])
  1268. );
  1269. $this->assertStatusCode($this->response, $statusCode);
  1270. sleep(1); // make sure Postgres manages the order of the messages
  1271. $response = $this->getDataFromResponse($this->response);
  1272. if (isset($response['id'])) {
  1273. self::$messages[$message] = $response['id'];
  1274. }
  1275. Assert::assertStringStartsWith($response['referenceId'], $referenceId);
  1276. }
  1277. /**
  1278. * @Then /^user "([^"]*)" sends reply "([^"]*)" on message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1279. *
  1280. * @param string $user
  1281. * @param string $reply
  1282. * @param string $message
  1283. * @param string $identifier
  1284. * @param string $statusCode
  1285. * @param string $apiVersion
  1286. */
  1287. public function userSendsReplyToRoom($user, $reply, $message, $identifier, $statusCode, $apiVersion = 'v1') {
  1288. $replyTo = self::$messages[$message];
  1289. $this->setCurrentUser($user);
  1290. $this->sendRequest(
  1291. 'POST', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier],
  1292. new TableNode([['message', $reply], ['replyTo', $replyTo]])
  1293. );
  1294. $this->assertStatusCode($this->response, $statusCode);
  1295. sleep(1); // make sure Postgres manages the order of the messages
  1296. $response = $this->getDataFromResponse($this->response);
  1297. if (isset($response['id'])) {
  1298. self::$messages[$reply] = $response['id'];
  1299. }
  1300. }
  1301. /**
  1302. * @Then /^user "([^"]*)" sees the following messages in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1303. *
  1304. * @param string $user
  1305. * @param string $identifier
  1306. * @param string $statusCode
  1307. * @param string $apiVersion
  1308. */
  1309. public function userSeesTheFollowingMessagesInRoom($user, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1310. $this->setCurrentUser($user);
  1311. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
  1312. $this->assertStatusCode($this->response, $statusCode);
  1313. $this->compareDataResponse($formData);
  1314. }
  1315. /**
  1316. * @Then /^user "([^"]*)" received a system messages in room "([^"]*)" to delete "([^"]*)"(?: \((v1)\))?$/
  1317. *
  1318. * @param string $user
  1319. * @param string $identifier
  1320. * @param string $message
  1321. * @param string $apiVersion
  1322. */
  1323. public function userReceivedDeleteMessage($user, $identifier, $message, $apiVersion = 'v1') {
  1324. $this->setCurrentUser($user);
  1325. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
  1326. $this->assertStatusCode($this->response, 200);
  1327. $actual = $this->getDataFromResponse($this->response);
  1328. foreach ($actual as $m) {
  1329. if ($m['systemMessage'] === 'message_deleted') {
  1330. if (isset($m['parent']['id']) && $m['parent']['id'] === self::$messages[$message]) {
  1331. return;
  1332. }
  1333. }
  1334. }
  1335. Assert::fail('Missing message_deleted system message for "' . $message . '"');
  1336. }
  1337. /**
  1338. * @Then /^user "([^"]*)" sees the following messages in room "([^"]*)" starting with "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1339. *
  1340. * @param string $user
  1341. * @param string $identifier
  1342. * @param string $knownMessage
  1343. * @param string $statusCode
  1344. * @param string $apiVersion
  1345. * @param TableNode|null $formData
  1346. */
  1347. public function userAwaitsTheFollowingMessagesInRoom($user, $identifier, $knownMessage, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1348. $this->setCurrentUser($user);
  1349. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=1&includeLastKnown=1&lastKnownMessageId=' . self::$messages[$knownMessage]);
  1350. $this->assertStatusCode($this->response, $statusCode);
  1351. $this->compareDataResponse($formData);
  1352. }
  1353. /**
  1354. * @param TableNode|null $formData
  1355. */
  1356. protected function compareDataResponse(TableNode $formData = null) {
  1357. $actual = $this->getDataFromResponse($this->response);
  1358. $messages = [];
  1359. array_map(function (array $message) use (&$messages) {
  1360. // Filter out system messages
  1361. if ($message['systemMessage'] === '') {
  1362. $messages[] = $message;
  1363. }
  1364. }, $actual);
  1365. foreach ($messages as $message) {
  1366. // Include the received messages in the list of messages used for
  1367. // replies; this is needed to get special messages not explicitly
  1368. // sent like those for shared files.
  1369. self::$messages[$message['message']] = $message['id'];
  1370. }
  1371. if ($formData === null) {
  1372. Assert::assertEmpty($messages);
  1373. return;
  1374. }
  1375. $includeParents = in_array('parentMessage', $formData->getRow(0), true);
  1376. $includeReferenceId = in_array('referenceId', $formData->getRow(0), true);
  1377. $includeReactions = in_array('reactions', $formData->getRow(0), true);
  1378. $count = count($formData->getHash());
  1379. Assert::assertCount($count, $messages, 'Message count does not match');
  1380. for ($i = 0; $i < $count; $i++) {
  1381. if ($formData->getHash()[$i]['messageParameters'] === '"IGNORE"') {
  1382. $messages[$i]['messageParameters'] = 'IGNORE';
  1383. }
  1384. }
  1385. Assert::assertEquals($formData->getHash(), array_map(function ($message) use ($includeParents, $includeReferenceId, $includeReactions) {
  1386. $data = [
  1387. 'room' => self::$tokenToIdentifier[$message['token']],
  1388. 'actorType' => $message['actorType'],
  1389. 'actorId' => ($message['actorType'] === 'guests')? self::$sessionIdToUser[$message['actorId']]: $message['actorId'],
  1390. 'actorDisplayName' => $message['actorDisplayName'],
  1391. // TODO test timestamp; it may require using Runkit, php-timecop
  1392. // or something like that.
  1393. 'message' => $message['message'],
  1394. 'messageParameters' => json_encode($message['messageParameters']),
  1395. ];
  1396. if ($includeParents) {
  1397. $data['parentMessage'] = $message['parent']['message'] ?? '';
  1398. }
  1399. if ($includeReferenceId) {
  1400. $data['referenceId'] = $message['referenceId'];
  1401. }
  1402. if ($includeReactions) {
  1403. $data['reactions'] = json_encode($message['reactions'], JSON_UNESCAPED_UNICODE);
  1404. }
  1405. return $data;
  1406. }, $messages));
  1407. }
  1408. /**
  1409. * @Then /^user "([^"]*)" sees the following system messages in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1410. *
  1411. * @param string $user
  1412. * @param string $identifier
  1413. * @param string $statusCode
  1414. * @param string $apiVersion
  1415. */
  1416. public function userSeesTheFollowingSystemMessagesInRoom($user, $identifier, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1417. $this->setCurrentUser($user);
  1418. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '?lookIntoFuture=0');
  1419. $this->assertStatusCode($this->response, $statusCode);
  1420. $messages = $this->getDataFromResponse($this->response);
  1421. $messages = array_filter($messages, function (array $message) {
  1422. return $message['systemMessage'] !== '';
  1423. });
  1424. foreach ($messages as $systemMessage) {
  1425. // Include the received system messages in the list of messages used
  1426. // for replies.
  1427. self::$messages[$systemMessage['systemMessage']] = $systemMessage['id'];
  1428. }
  1429. if ($formData === null) {
  1430. Assert::assertEmpty($messages);
  1431. return;
  1432. }
  1433. Assert::assertCount(count($formData->getHash()), $messages, 'Message count does not match');
  1434. Assert::assertEquals($formData->getHash(), array_map(function ($message) {
  1435. return [
  1436. 'room' => self::$tokenToIdentifier[$message['token']],
  1437. 'actorType' => (string) $message['actorType'],
  1438. 'actorId' => ($message['actorType'] === 'guests')? self::$sessionIdToUser[$message['actorId']]: (string) $message['actorId'],
  1439. 'actorDisplayName' => (string) $message['actorDisplayName'],
  1440. 'systemMessage' => (string) $message['systemMessage'],
  1441. ];
  1442. }, $messages));
  1443. }
  1444. /**
  1445. * @Then /^user "([^"]*)" gets the following candidate mentions in room "([^"]*)" for "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1446. *
  1447. * @param string $user
  1448. * @param string $identifier
  1449. * @param string $search
  1450. * @param string $statusCode
  1451. * @param string $apiVersion
  1452. * @param TableNode|null $formData
  1453. */
  1454. public function userGetsTheFollowingCandidateMentionsInRoomFor($user, $identifier, $search, $statusCode, $apiVersion = 'v1', TableNode $formData = null) {
  1455. $this->setCurrentUser($user);
  1456. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/chat/' . self::$identifierToToken[$identifier] . '/mentions?search=' . $search);
  1457. $this->assertStatusCode($this->response, $statusCode);
  1458. $mentions = $this->getDataFromResponse($this->response);
  1459. if ($formData === null) {
  1460. Assert::assertEmpty($mentions);
  1461. return;
  1462. }
  1463. Assert::assertCount(count($formData->getHash()), $mentions, 'Mentions count does not match');
  1464. usort($mentions, function ($a, $b) {
  1465. if ($a['source'] === $b['source']) {
  1466. return $a['label'] <=> $b['label'];
  1467. }
  1468. return $a['source'] <=> $b['source'];
  1469. });
  1470. $expected = $formData->getHash();
  1471. usort($expected, function ($a, $b) {
  1472. if ($a['source'] === $b['source']) {
  1473. return $a['label'] <=> $b['label'];
  1474. }
  1475. return $a['source'] <=> $b['source'];
  1476. });
  1477. foreach ($expected as $key => $row) {
  1478. if ($row['id'] === 'GUEST_ID') {
  1479. Assert::assertRegExp('/^guest\/[0-9a-f]{40}$/', $mentions[$key]['id']);
  1480. $mentions[$key]['id'] = 'GUEST_ID';
  1481. }
  1482. Assert::assertEquals($row, $mentions[$key]);
  1483. }
  1484. }
  1485. /**
  1486. * @Then /^guest "([^"]*)" sets name to "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1487. *
  1488. * @param string $user
  1489. * @param string $name
  1490. * @param string $identifier
  1491. * @param string $statusCode
  1492. * @param string $apiVersion
  1493. */
  1494. public function guestSetsName($user, $name, $identifier, $statusCode, $apiVersion = 'v1') {
  1495. $this->setCurrentUser($user);
  1496. $this->sendRequest(
  1497. 'POST', '/apps/spreed/api/' . $apiVersion . '/guest/' . self::$identifierToToken[$identifier] . '/name',
  1498. new TableNode([['displayName', $name]])
  1499. );
  1500. $this->assertStatusCode($this->response, $statusCode);
  1501. }
  1502. /**
  1503. * @Then /^last response has (no) last common read message header$/
  1504. *
  1505. * @param string $no
  1506. */
  1507. public function hasNoChatLastCommonReadHeader($no) {
  1508. Assert::assertArrayNotHasKey('X-Chat-Last-Common-Read', $this->response->getHeaders(), 'X-Chat-Last-Common-Read is set to ' . ($this->response->getHeader('X-Chat-Last-Common-Read')[0] ?? '0'));
  1509. }
  1510. /**
  1511. * @Then /^last response has last common read message header (set to|less than) "([^"]*)"$/
  1512. *
  1513. * @param string $setOrLower
  1514. * @param string $message
  1515. */
  1516. public function hasChatLastCommonReadHeader($setOrLower, $message) {
  1517. Assert::assertArrayHasKey('X-Chat-Last-Common-Read', $this->response->getHeaders());
  1518. if ($setOrLower === 'set to') {
  1519. Assert::assertEquals(self::$messages[$message], $this->response->getHeader('X-Chat-Last-Common-Read')[0]);
  1520. } else {
  1521. // Less than might be required for the first message, because the last read message before is the join/room creation message and we don't know that ID
  1522. Assert::assertLessThan(self::$messages[$message], $this->response->getHeader('X-Chat-Last-Common-Read')[0]);
  1523. }
  1524. }
  1525. /**
  1526. * @Then /^user "([^"]*)" sets setting "([^"]*)" to "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1527. *
  1528. * @param string $user
  1529. * @param string $setting
  1530. * @param string $value
  1531. * @param string $statusCode
  1532. * @param string $apiVersion
  1533. */
  1534. public function userSetting($user, $setting, $value, $statusCode, $apiVersion = 'v1') {
  1535. $this->setCurrentUser($user);
  1536. $this->sendRequest(
  1537. 'POST', '/apps/spreed/api/' . $apiVersion . '/settings/user',
  1538. new TableNode([['key', $setting], ['value', $value]])
  1539. );
  1540. $this->assertStatusCode($this->response, $statusCode);
  1541. }
  1542. /**
  1543. * @Then /^user "([^"]*)" has capability "([^"]*)" set to "([^"]*)"$/
  1544. *
  1545. * @param string $user
  1546. * @param string $capability
  1547. * @param string $value
  1548. */
  1549. public function userCheckCapability($user, $capability, $value) {
  1550. $this->setCurrentUser($user);
  1551. $this->sendRequest(
  1552. 'GET', '/cloud/capabilities'
  1553. );
  1554. $data = $this->getDataFromResponse($this->response);
  1555. $capabilities = $data['capabilities'];
  1556. $keys = explode('=>', $capability);
  1557. $finalKey = array_pop($keys);
  1558. $cur = $capabilities;
  1559. foreach ($keys as $key) {
  1560. Assert::assertArrayHasKey($key, $cur);
  1561. $cur = $cur[$key];
  1562. }
  1563. Assert::assertEquals($value, $cur[$finalKey]);
  1564. }
  1565. /**
  1566. * Parses the xml answer to get the array of users returned.
  1567. * @param ResponseInterface $response
  1568. * @return array
  1569. */
  1570. protected function getDataFromResponse(ResponseInterface $response) {
  1571. $jsonBody = json_decode($response->getBody()->getContents(), true);
  1572. return $jsonBody['ocs']['data'];
  1573. }
  1574. /**
  1575. * @Then /^status code is ([0-9]*)$/
  1576. *
  1577. * @param int $statusCode
  1578. */
  1579. public function isStatusCode($statusCode) {
  1580. $this->assertStatusCode($this->response, $statusCode);
  1581. }
  1582. /**
  1583. * @Given the following :appId app config is set
  1584. *
  1585. * @param TableNode $formData
  1586. */
  1587. public function setAppConfig(string $appId, TableNode $formData): void {
  1588. $currentUser = $this->currentUser;
  1589. $this->setCurrentUser('admin');
  1590. foreach ($formData->getRows() as $row) {
  1591. $this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/' . $appId . '/' . $row[0], [
  1592. 'value' => $row[1],
  1593. ]);
  1594. $this->changedConfigs[$appId][] = $row[0];
  1595. }
  1596. $this->setCurrentUser($currentUser);
  1597. }
  1598. /**
  1599. * @Given /^guest accounts can be created$/
  1600. *
  1601. * @param TableNode $formData
  1602. */
  1603. public function allowGuestAccountsCreation(): void {
  1604. $currentUser = $this->currentUser;
  1605. $this->setCurrentUser('admin');
  1606. // save old state and restore at the end
  1607. $this->sendRequest('GET', '/cloud/apps?filter=enabled');
  1608. $this->assertStatusCode($this->response, 200);
  1609. $data = $this->getDataFromResponse($this->response);
  1610. $this->guestsAppWasEnabled = in_array('guests', $data['apps'], true);
  1611. if (!$this->guestsAppWasEnabled) {
  1612. // enable guests app
  1613. /*
  1614. $this->sendRequest('POST', '/cloud/apps/guests');
  1615. $this->assertStatusCode($this->response, 200);
  1616. */
  1617. // seems using provisioning API doesn't create tables...
  1618. $this->runOcc(['app:enable', 'guests']);
  1619. }
  1620. // save previously set whitelist
  1621. $this->sendRequest('GET', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist');
  1622. $this->assertStatusCode($this->response, 200);
  1623. $this->guestsOldWhitelist = $this->getDataFromResponse($this->response)['data'];
  1624. // set whitelist to allow spreed only
  1625. $this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist', [
  1626. 'value' => 'spreed',
  1627. ]);
  1628. $this->setCurrentUser($currentUser);
  1629. }
  1630. /**
  1631. * @BeforeScenario
  1632. * @AfterScenario
  1633. */
  1634. public function resetSpreedAppData() {
  1635. $currentUser = $this->currentUser;
  1636. $this->setCurrentUser('admin');
  1637. $this->sendRequest('DELETE', '/apps/spreedcheats/');
  1638. foreach ($this->changedConfigs as $appId => $configs) {
  1639. foreach ($configs as $config) {
  1640. $this->sendRequest('DELETE', '/apps/provisioning_api/api/v1/config/apps/' . $appId . '/' . $config);
  1641. }
  1642. }
  1643. $this->setCurrentUser($currentUser);
  1644. }
  1645. /**
  1646. * @AfterScenario
  1647. */
  1648. public function resetGuestsAppState() {
  1649. if ($this->guestsAppWasEnabled === null) {
  1650. // guests app was not touched
  1651. return;
  1652. }
  1653. $currentUser = $this->currentUser;
  1654. $this->setCurrentUser('admin');
  1655. if ($this->guestsOldWhitelist) {
  1656. // restore old whitelist
  1657. $this->sendRequest('POST', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist', [
  1658. 'value' => $this->guestsOldWhitelist,
  1659. ]);
  1660. } else {
  1661. // restore to default
  1662. $this->sendRequest('DELETE', '/apps/provisioning_api/api/v1/config/apps/guests/whitelist');
  1663. }
  1664. // restore app's enabled state
  1665. $this->sendRequest($this->guestsAppWasEnabled ? 'POST' : 'DELETE', '/cloud/apps/guests');
  1666. $this->setCurrentUser($currentUser);
  1667. $this->guestsAppWasEnabled = null;
  1668. }
  1669. /*
  1670. * User management
  1671. */
  1672. /**
  1673. * @Given /^as user "([^"]*)"$/
  1674. * @param string $user
  1675. */
  1676. public function setCurrentUser($user) {
  1677. $this->currentUser = $user;
  1678. }
  1679. /**
  1680. * @Given /^user "([^"]*)" exists$/
  1681. * @param string $user
  1682. */
  1683. public function assureUserExists($user) {
  1684. $response = $this->userExists($user);
  1685. if ($response->getStatusCode() !== 200) {
  1686. $this->createUser($user);
  1687. // Set a display name different than the user ID to be able to
  1688. // ensure in the tests that the right value was returned.
  1689. $this->setUserDisplayName($user);
  1690. $response = $this->userExists($user);
  1691. $this->assertStatusCode($response, 200);
  1692. }
  1693. }
  1694. /**
  1695. * @Given /^user "([^"]*)" is a guest account user/
  1696. * @param string $email email address
  1697. */
  1698. public function createGuestUser($email) {
  1699. $currentUser = $this->currentUser;
  1700. $this->setCurrentUser('admin');
  1701. // in case it exists
  1702. $this->deleteUser($email);
  1703. $lastCode = $this->runOcc([
  1704. 'guests:add',
  1705. // creator user
  1706. 'admin',
  1707. // email
  1708. $email,
  1709. '--display-name',
  1710. $email . '-displayname',
  1711. '--password-from-env',
  1712. ], [
  1713. 'OC_PASS' => self::TEST_PASSWORD,
  1714. ]);
  1715. Assert::assertEquals(0, $lastCode, 'Guest creation succeeded for ' . $email);
  1716. $this->createdGuestAccountUsers[] = $email;
  1717. $this->setCurrentUser($currentUser);
  1718. }
  1719. private function userExists($user) {
  1720. $currentUser = $this->currentUser;
  1721. $this->setCurrentUser('admin');
  1722. $this->sendRequest('GET', '/cloud/users/' . $user);
  1723. $this->setCurrentUser($currentUser);
  1724. return $this->response;
  1725. }
  1726. private function createUser($user) {
  1727. $currentUser = $this->currentUser;
  1728. $this->setCurrentUser('admin');
  1729. $this->sendRequest('POST', '/cloud/users', [
  1730. 'userid' => $user,
  1731. 'password' => self::TEST_PASSWORD,
  1732. ]);
  1733. $this->assertStatusCode($this->response, 200, 'Failed to create user');
  1734. //Quick hack to login once with the current user
  1735. $this->setCurrentUser($user);
  1736. $this->sendRequest('GET', '/cloud/users' . '/' . $user);
  1737. $this->assertStatusCode($this->response, 200, 'Failed to do first login');
  1738. $this->createdUsers[] = $user;
  1739. $this->setCurrentUser($currentUser);
  1740. }
  1741. /**
  1742. * @Given /^user "([^"]*)" is deleted$/
  1743. * @param string $user
  1744. */
  1745. public function userIsDeleted($user) {
  1746. $deleted = false;
  1747. $this->deleteUser($user);
  1748. $response = $this->userExists($user);
  1749. $deleted = $response->getStatusCode() === 404;
  1750. if (!$deleted) {
  1751. Assert::fail("User $user exists");
  1752. }
  1753. }
  1754. private function deleteUser($user) {
  1755. $currentUser = $this->currentUser;
  1756. $this->setCurrentUser('admin');
  1757. $this->sendRequest('DELETE', '/cloud/users/' . $user);
  1758. $this->setCurrentUser($currentUser);
  1759. unset($this->createdUsers[array_search($user, $this->createdUsers, true)]);
  1760. return $this->response;
  1761. }
  1762. private function setUserDisplayName($user) {
  1763. $currentUser = $this->currentUser;
  1764. $this->setCurrentUser('admin');
  1765. $this->sendRequest('PUT', '/cloud/users/' . $user, [
  1766. 'key' => 'displayname',
  1767. 'value' => $user . '-displayname'
  1768. ]);
  1769. $this->setCurrentUser($currentUser);
  1770. }
  1771. /**
  1772. * @Given /^group "([^"]*)" exists$/
  1773. * @param string $group
  1774. */
  1775. public function assureGroupExists($group) {
  1776. $response = $this->groupExists($group);
  1777. if ($response->getStatusCode() !== 200) {
  1778. $this->createGroup($group);
  1779. $response = $this->groupExists($group);
  1780. $this->assertStatusCode($response, 200);
  1781. }
  1782. }
  1783. private function groupExists($group) {
  1784. $currentUser = $this->currentUser;
  1785. $this->setCurrentUser('admin');
  1786. $this->sendRequest('GET', '/cloud/groups/' . $group);
  1787. $this->setCurrentUser($currentUser);
  1788. return $this->response;
  1789. }
  1790. private function createGroup($group) {
  1791. $currentUser = $this->currentUser;
  1792. $this->setCurrentUser('admin');
  1793. $this->sendRequest('POST', '/cloud/groups', [
  1794. 'groupid' => $group,
  1795. ]);
  1796. $this->setCurrentUser($currentUser);
  1797. $this->createdGroups[] = $group;
  1798. }
  1799. private function deleteGroup($group) {
  1800. $currentUser = $this->currentUser;
  1801. $this->setCurrentUser('admin');
  1802. $this->sendRequest('DELETE', '/cloud/groups/' . $group);
  1803. $this->setCurrentUser($currentUser);
  1804. unset($this->createdGroups[array_search($group, $this->createdGroups, true)]);
  1805. }
  1806. /**
  1807. * @When /^user "([^"]*)" is member of group "([^"]*)"$/
  1808. * @param string $user
  1809. * @param string $group
  1810. */
  1811. public function addingUserToGroup($user, $group) {
  1812. $currentUser = $this->currentUser;
  1813. $this->setCurrentUser('admin');
  1814. $this->sendRequest('POST', "/cloud/users/$user/groups", [
  1815. 'groupid' => $group,
  1816. ]);
  1817. $this->assertStatusCode($this->response, 200);
  1818. $this->setCurrentUser($currentUser);
  1819. }
  1820. /**
  1821. * @When /^user "([^"]*)" is not member of group "([^"]*)"$/
  1822. * @param string $user
  1823. * @param string $group
  1824. */
  1825. public function removeUserFromGroup($user, $group) {
  1826. $currentUser = $this->currentUser;
  1827. $this->setCurrentUser('admin');
  1828. $this->sendRequest('DELETE', "/cloud/users/$user/groups", [
  1829. 'groupid' => $group,
  1830. ]);
  1831. $this->assertStatusCode($this->response, 200);
  1832. $this->setCurrentUser($currentUser);
  1833. }
  1834. /**
  1835. * @Given /^user "([^"]*)" (delete react|react) with "([^"]*)" on message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1836. */
  1837. public function userReactWithOnMessageToRoomWith(string $user, string $action, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
  1838. $token = self::$identifierToToken[$identifier];
  1839. $messageId = self::$messages[$message];
  1840. $this->setCurrentUser($user);
  1841. $verb = $action === 'react' ? 'POST' : 'DELETE';
  1842. $this->sendRequest($verb, '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId, [
  1843. 'reaction' => $reaction
  1844. ]);
  1845. $this->assertStatusCode($this->response, $statusCode);
  1846. }
  1847. /**
  1848. * @Given /^user "([^"]*)" retrieve reactions "([^"]*)" of message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
  1849. */
  1850. public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData): void {
  1851. $token = self::$identifierToToken[$identifier];
  1852. $messageId = self::$messages[$message];
  1853. $this->setCurrentUser($user);
  1854. $reaction = $reaction !== 'all' ? '?reaction=' . $reaction : '';
  1855. $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId . $reaction);
  1856. $this->assertStatusCode($this->response, $statusCode);
  1857. $this->assertReactionList($formData);
  1858. }
  1859. private function assertReactionList(TableNode $formData): void {
  1860. $expected = [];
  1861. foreach ($formData->getHash() as $row) {
  1862. $reaction = $row['reaction'];
  1863. unset($row['reaction']);
  1864. $expected[$reaction][] = $row;
  1865. }
  1866. $result = $this->getDataFromResponse($this->response);
  1867. $result = array_map(static function ($reaction, $list) use ($expected): array {
  1868. $list = array_map(function ($reaction) {
  1869. unset($reaction['timestamp']);
  1870. return $reaction;
  1871. }, $list);
  1872. Assert::assertCount(count($list), $expected[$reaction], 'Reaction count by type does not match');
  1873. usort($expected[$reaction], [self::class, 'sortAttendees']);
  1874. usort($list, [self::class, 'sortAttendees']);
  1875. Assert::assertEquals($expected[$reaction], $list, 'Reaction list by type does not match');
  1876. return $list;
  1877. }, array_keys($result), array_values($result));
  1878. Assert::assertCount(count($expected), $result, 'Reaction count does not match');
  1879. }
  1880. /*
  1881. * Requests
  1882. */
  1883. /**
  1884. * @Given /^user "([^"]*)" logs in$/
  1885. * @param string $user
  1886. */
  1887. public function userLogsIn(string $user) {
  1888. $loginUrl = $this->baseUrl . '/login';
  1889. $cookieJar = $this->getUserCookieJar($user);
  1890. // Request a new session and extract CSRF token
  1891. $client = new Client();
  1892. $this->response = $client->get(
  1893. $loginUrl,
  1894. [
  1895. 'cookies' => $cookieJar,
  1896. ]
  1897. );
  1898. $requestToken = $this->extractRequestTokenFromResponse($this->response);
  1899. // Login and extract new token
  1900. $password = ($user === 'admin') ? 'admin' : self::TEST_PASSWORD;
  1901. $client = new Client();
  1902. $this->response = $client->post(
  1903. $loginUrl,
  1904. [
  1905. 'form_params' => [
  1906. 'user' => $user,
  1907. 'password' => $password,
  1908. 'requesttoken' => $requestToken,
  1909. ],
  1910. 'cookies' => $cookieJar,
  1911. ]
  1912. );
  1913. $this->assertStatusCode($this->response, 200);
  1914. }
  1915. /**
  1916. * @param ResponseInterface $response
  1917. * @return string
  1918. */
  1919. private function extractRequestTokenFromResponse(ResponseInterface $response): string {
  1920. return substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
  1921. }
  1922. /**
  1923. * @When /^sending "([^"]*)" to "([^"]*)" with$/
  1924. * @param string $verb
  1925. * @param string $url
  1926. * @param TableNode|array|null $body
  1927. * @param array $headers
  1928. */
  1929. public function sendRequest($verb, $url, $body = null, array $headers = []) {
  1930. $fullUrl = $this->baseUrl . 'ocs/v2.php' . $url;
  1931. $client = new Client();
  1932. $options = ['cookies' => $this->getUserCookieJar($this->currentUser)];
  1933. if ($this->currentUser === 'admin') {
  1934. $options['auth'] = ['admin', 'admin'];
  1935. } elseif (strpos($this->currentUser, 'guest') !== 0) {
  1936. $options['auth'] = [$this->currentUser, self::TEST_PASSWORD];
  1937. }
  1938. if ($body instanceof TableNode) {
  1939. $fd = $body->getRowsHash();
  1940. $options['form_params'] = $fd;
  1941. } elseif (is_array($body)) {
  1942. $options['form_params'] = $body;
  1943. }
  1944. $options['headers'] = array_merge($headers, [
  1945. 'OCS-ApiRequest' => 'true',
  1946. 'Accept' => 'application/json',
  1947. ]);
  1948. try {
  1949. $this->response = $client->{$verb}($fullUrl, $options);
  1950. } catch (ClientException $ex) {
  1951. $this->response = $ex->getResponse();
  1952. } catch (\GuzzleHttp\Exception\ServerException $ex) {
  1953. $this->response = $ex->getResponse();
  1954. }
  1955. }
  1956. protected function getUserCookieJar($user) {
  1957. if (!isset($this->cookieJars[$user])) {
  1958. $this->cookieJars[$user] = new CookieJar();
  1959. }
  1960. return $this->cookieJars[$user];
  1961. }
  1962. /**
  1963. * @param ResponseInterface $response
  1964. * @param int $statusCode
  1965. * @param string $message
  1966. */
  1967. protected function assertStatusCode(ResponseInterface $response, int $statusCode, string $message = '') {
  1968. Assert::assertEquals($statusCode, $response->getStatusCode(), $message);
  1969. }
  1970. }