SystemAddressBookTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * @copyright 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
  5. *
  6. * @author 2023 Christoph Wurst <christoph@winzerhof-wurst.at>
  7. *
  8. * @license GNU AGPL version 3 or any later version
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as
  12. * published by the Free Software Foundation, either version 3 of the
  13. * License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. namespace OCA\DAV\Tests\unit\CardDAV;
  24. use OC\AppFramework\Http\Request;
  25. use OCA\DAV\CardDAV\SyncService;
  26. use OCA\DAV\CardDAV\SystemAddressbook;
  27. use OCA\Federation\TrustedServers;
  28. use OCP\Accounts\IAccountManager;
  29. use OCP\IConfig;
  30. use OCP\IGroup;
  31. use OCP\IGroupManager;
  32. use OCP\IL10N;
  33. use OCP\IRequest;
  34. use OCP\IUser;
  35. use OCP\IUserSession;
  36. use PHPUnit\Framework\MockObject\MockObject;
  37. use Sabre\CardDAV\Backend\BackendInterface;
  38. use Sabre\DAV\Exception\Forbidden;
  39. use Sabre\DAV\Exception\NotFound;
  40. use Sabre\VObject\Component\VCard;
  41. use Sabre\VObject\Reader;
  42. use Test\TestCase;
  43. class SystemAddressBookTest extends TestCase {
  44. private MockObject|BackendInterface $cardDavBackend;
  45. private array $addressBookInfo;
  46. private IL10N|MockObject $l10n;
  47. private IConfig|MockObject $config;
  48. private IUserSession $userSession;
  49. private IRequest|MockObject $request;
  50. private array $server;
  51. private TrustedServers|MockObject $trustedServers;
  52. private IGroupManager|MockObject $groupManager;
  53. private SystemAddressbook $addressBook;
  54. protected function setUp(): void {
  55. parent::setUp();
  56. $this->cardDavBackend = $this->createMock(BackendInterface::class);
  57. $this->addressBookInfo = [
  58. 'id' => 123,
  59. '{DAV:}displayname' => 'Accounts',
  60. 'principaluri' => 'principals/system/system',
  61. ];
  62. $this->l10n = $this->createMock(IL10N::class);
  63. $this->config = $this->createMock(IConfig::class);
  64. $this->userSession = $this->createMock(IUserSession::class);
  65. $this->request = $this->createMock(Request::class);
  66. $this->server = [
  67. 'PHP_AUTH_USER' => 'system',
  68. 'PHP_AUTH_PW' => 'shared123',
  69. ];
  70. $this->request->method('__get')->with('server')->willReturn($this->server);
  71. $this->trustedServers = $this->createMock(TrustedServers::class);
  72. $this->groupManager = $this->createMock(IGroupManager::class);
  73. $this->addressBook = new SystemAddressbook(
  74. $this->cardDavBackend,
  75. $this->addressBookInfo,
  76. $this->l10n,
  77. $this->config,
  78. $this->userSession,
  79. $this->request,
  80. $this->trustedServers,
  81. $this->groupManager,
  82. );
  83. }
  84. public function testGetChildrenAsGuest(): void {
  85. $this->config->expects(self::exactly(3))
  86. ->method('getAppValue')
  87. ->willReturnMap([
  88. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  89. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  90. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  91. ]);
  92. $user = $this->createMock(IUser::class);
  93. $user->method('getUID')->willReturn('user');
  94. $user->method('getBackendClassName')->willReturn('Guests');
  95. $this->userSession->expects(self::once())
  96. ->method('getUser')
  97. ->willReturn($user);
  98. $vcfWithScopes = <<<VCF
  99. BEGIN:VCARD
  100. VERSION:3.0
  101. PRODID:-//Sabre//Sabre VObject 4.4.2//EN
  102. UID:admin
  103. FN;X-NC-SCOPE=v2-federated:admin
  104. N;X-NC-SCOPE=v2-federated:admin;;;;
  105. ADR;TYPE=OTHER;X-NC-SCOPE=v2-local:Testing test test test;;;;;;
  106. EMAIL;TYPE=OTHER;X-NC-SCOPE=v2-federated:miau_lalala@gmx.net
  107. TEL;TYPE=OTHER;X-NC-SCOPE=v2-local:+435454454544
  108. CLOUD:admin@http://localhost
  109. END:VCARD
  110. VCF;
  111. $originalCard = [
  112. 'carddata' => $vcfWithScopes,
  113. ];
  114. $this->cardDavBackend->expects(self::once())
  115. ->method('getCard')
  116. ->with(123, 'Guests:user.vcf')
  117. ->willReturn($originalCard);
  118. $children = $this->addressBook->getChildren();
  119. self::assertCount(1, $children);
  120. }
  121. public function testGetFilteredChildForFederation(): void {
  122. $this->config->expects(self::exactly(3))
  123. ->method('getAppValue')
  124. ->willReturnMap([
  125. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  126. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  127. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  128. ]);
  129. $this->trustedServers->expects(self::once())
  130. ->method('getServers')
  131. ->willReturn([
  132. [
  133. 'shared_secret' => 'shared123',
  134. ],
  135. ]);
  136. $vcfWithScopes = <<<VCF
  137. BEGIN:VCARD
  138. VERSION:3.0
  139. PRODID:-//Sabre//Sabre VObject 4.4.2//EN
  140. UID:admin
  141. FN;X-NC-SCOPE=v2-federated:admin
  142. N;X-NC-SCOPE=v2-federated:admin;;;;
  143. ADR;TYPE=OTHER;X-NC-SCOPE=v2-local:Testing test test test;;;;;;
  144. EMAIL;TYPE=OTHER;X-NC-SCOPE=v2-federated:miau_lalala@gmx.net
  145. TEL;TYPE=OTHER;X-NC-SCOPE=v2-local:+435454454544
  146. CLOUD:admin@http://localhost
  147. END:VCARD
  148. VCF;
  149. $originalCard = [
  150. 'carddata' => $vcfWithScopes,
  151. ];
  152. $this->cardDavBackend->expects(self::once())
  153. ->method('getCard')
  154. ->with(123, 'user.vcf')
  155. ->willReturn($originalCard);
  156. $card = $this->addressBook->getChild("user.vcf");
  157. /** @var VCard $vCard */
  158. $vCard = Reader::read($card->get());
  159. foreach ($vCard->children() as $child) {
  160. $scope = $child->offsetGet('X-NC-SCOPE');
  161. if ($scope !== null) {
  162. self::assertNotEquals(IAccountManager::SCOPE_PRIVATE, $scope->getValue());
  163. self::assertNotEquals(IAccountManager::SCOPE_LOCAL, $scope->getValue());
  164. }
  165. }
  166. }
  167. public function testGetChildNotFound(): void {
  168. $this->config->expects(self::exactly(3))
  169. ->method('getAppValue')
  170. ->willReturnMap([
  171. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  172. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  173. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  174. ]);
  175. $this->trustedServers->expects(self::once())
  176. ->method('getServers')
  177. ->willReturn([
  178. [
  179. 'shared_secret' => 'shared123',
  180. ],
  181. ]);
  182. $this->expectException(NotFound::class);
  183. $this->addressBook->getChild("LDAP:user.vcf");
  184. }
  185. public function testGetChildWithoutEnumeration(): void {
  186. $this->config->expects(self::exactly(3))
  187. ->method('getAppValue')
  188. ->willReturnMap([
  189. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
  190. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  191. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  192. ]);
  193. $this->expectException(Forbidden::class);
  194. $this->addressBook->getChild("LDAP:user.vcf");
  195. }
  196. public function testGetChildAsGuest(): void {
  197. $this->config->expects(self::exactly(3))
  198. ->method('getAppValue')
  199. ->willReturnMap([
  200. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  201. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  202. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  203. ]);
  204. $user = $this->createMock(IUser::class);
  205. $user->method('getBackendClassName')->willReturn('Guests');
  206. $this->userSession->expects(self::once())
  207. ->method('getUser')
  208. ->willReturn($user);
  209. $this->expectException(Forbidden::class);
  210. $this->addressBook->getChild("LDAP:user.vcf");
  211. }
  212. public function testGetChildWithGroupEnumerationRestriction(): void {
  213. $this->config->expects(self::exactly(3))
  214. ->method('getAppValue')
  215. ->willReturnMap([
  216. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  217. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
  218. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  219. ]);
  220. $user = $this->createMock(IUser::class);
  221. $user->method('getBackendClassName')->willReturn('LDAP');
  222. $this->userSession->expects(self::once())
  223. ->method('getUser')
  224. ->willReturn($user);
  225. $otherUser = $this->createMock(IUser::class);
  226. $user->method('getBackendClassName')->willReturn('LDAP');
  227. $otherUser->method('getUID')->willReturn('other');
  228. $group = $this->createMock(IGroup::class);
  229. $group->expects(self::once())
  230. ->method('getUsers')
  231. ->willReturn([$otherUser]);
  232. $this->groupManager->expects(self::once())
  233. ->method('getUserGroups')
  234. ->with($user)
  235. ->willReturn([$group]);
  236. $cardData = <<<VCF
  237. BEGIN:VCARD
  238. VERSION:3.0
  239. PRODID:-//Sabre//Sabre VObject 4.4.2//EN
  240. UID:admin
  241. FN;X-NC-SCOPE=v2-federated:other
  242. END:VCARD
  243. VCF;
  244. $this->cardDavBackend->expects(self::once())
  245. ->method('getCard')
  246. ->with($this->addressBookInfo['id'], "{$otherUser->getBackendClassName()}:{$otherUser->getUID()}.vcf")
  247. ->willReturn([
  248. 'id' => 123,
  249. 'carddata' => $cardData,
  250. ]);
  251. $this->addressBook->getChild("{$otherUser->getBackendClassName()}:{$otherUser->getUID()}.vcf");
  252. }
  253. public function testGetChildWithPhoneNumberEnumerationRestriction(): void {
  254. $this->config->expects(self::exactly(3))
  255. ->method('getAppValue')
  256. ->willReturnMap([
  257. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  258. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  259. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
  260. ]);
  261. $user = $this->createMock(IUser::class);
  262. $user->method('getBackendClassName')->willReturn('LDAP');
  263. $this->userSession->expects(self::once())
  264. ->method('getUser')
  265. ->willReturn($user);
  266. $this->expectException(Forbidden::class);
  267. $this->addressBook->getChild("LDAP:user.vcf");
  268. }
  269. public function testGetOwnChildWithPhoneNumberEnumerationRestriction(): void {
  270. $this->config->expects(self::exactly(3))
  271. ->method('getAppValue')
  272. ->willReturnMap([
  273. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  274. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  275. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'yes'],
  276. ]);
  277. $user = $this->createMock(IUser::class);
  278. $user->method('getBackendClassName')->willReturn('LDAP');
  279. $user->method('getUID')->willReturn('user');
  280. $this->userSession->expects(self::once())
  281. ->method('getUser')
  282. ->willReturn($user);
  283. $cardData = <<<VCF
  284. BEGIN:VCARD
  285. VERSION:3.0
  286. PRODID:-//Sabre//Sabre VObject 4.4.2//EN
  287. UID:admin
  288. FN;X-NC-SCOPE=v2-federated:user
  289. END:VCARD
  290. VCF;
  291. $this->cardDavBackend->expects(self::once())
  292. ->method('getCard')
  293. ->with($this->addressBookInfo['id'], 'LDAP:user.vcf')
  294. ->willReturn([
  295. 'id' => 123,
  296. 'carddata' => $cardData,
  297. ]);
  298. $this->addressBook->getChild("LDAP:user.vcf");
  299. }
  300. public function testGetMultipleChildrenWithGroupEnumerationRestriction(): void {
  301. $this->config
  302. ->method('getAppValue')
  303. ->willReturnMap([
  304. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  305. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes'],
  306. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  307. ]);
  308. $user = $this->createMock(IUser::class);
  309. $user->method('getUID')->willReturn('user');
  310. $user->method('getBackendClassName')->willReturn('LDAP');
  311. $other1 = $this->createMock(IUser::class);
  312. $other1->method('getUID')->willReturn('other1');
  313. $other1->method('getBackendClassName')->willReturn('LDAP');
  314. $other2 = $this->createMock(IUser::class);
  315. $other2->method('getUID')->willReturn('other2');
  316. $other2->method('getBackendClassName')->willReturn('LDAP');
  317. $other3 = $this->createMock(IUser::class);
  318. $other3->method('getUID')->willReturn('other3');
  319. $other3->method('getBackendClassName')->willReturn('LDAP');
  320. $this->userSession
  321. ->method('getUser')
  322. ->willReturn($user);
  323. $group1 = $this->createMock(IGroup::class);
  324. $group1
  325. ->method('getUsers')
  326. ->willReturn([$user, $other1]);
  327. $group2 = $this->createMock(IGroup::class);
  328. $group2
  329. ->method('getUsers')
  330. ->willReturn([$other1, $other2, $user]);
  331. $this->groupManager
  332. ->method('getUserGroups')
  333. ->with($user)
  334. ->willReturn([$group1]);
  335. $this->cardDavBackend->expects(self::once())
  336. ->method('getMultipleCards')
  337. ->with($this->addressBookInfo['id'], [
  338. SyncService::getCardUri($user),
  339. SyncService::getCardUri($other1),
  340. ])
  341. ->willReturn([
  342. [],
  343. [],
  344. ]);
  345. $cards = $this->addressBook->getMultipleChildren([
  346. SyncService::getCardUri($user),
  347. SyncService::getCardUri($other1),
  348. // SyncService::getCardUri($other2), // Omitted to test that it's not returned as stray
  349. SyncService::getCardUri($other3), // No overlapping group with this one
  350. ]);
  351. self::assertCount(2, $cards);
  352. }
  353. public function testGetMultipleChildrenAsGuest(): void {
  354. $this->config
  355. ->method('getAppValue')
  356. ->willReturnMap([
  357. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  358. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  359. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  360. ]);
  361. $user = $this->createMock(IUser::class);
  362. $user->method('getUID')->willReturn('user');
  363. $user->method('getBackendClassName')->willReturn('Guests');
  364. $this->userSession->expects(self::once())
  365. ->method('getUser')
  366. ->willReturn($user);
  367. $cards = $this->addressBook->getMultipleChildren(['Database:user1.vcf', 'LDAP:user2.vcf']);
  368. self::assertEmpty($cards);
  369. }
  370. public function testGetMultipleChildren(): void {
  371. $this->config
  372. ->method('getAppValue')
  373. ->willReturnMap([
  374. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  375. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no'],
  376. ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'],
  377. ]);
  378. $this->trustedServers
  379. ->method('getServers')
  380. ->willReturn([
  381. [
  382. 'shared_secret' => 'shared123',
  383. ],
  384. ]);
  385. $cardData = <<<VCF
  386. BEGIN:VCARD
  387. VERSION:3.0
  388. PRODID:-//Sabre//Sabre VObject 4.4.2//EN
  389. UID:admin
  390. FN;X-NC-SCOPE=v2-federated:user
  391. END:VCARD
  392. VCF;
  393. $this->cardDavBackend->expects(self::once())
  394. ->method('getMultipleCards')
  395. ->with($this->addressBookInfo['id'], ['Database:user1.vcf', 'LDAP:user2.vcf'])
  396. ->willReturn([
  397. [
  398. 'id' => 123,
  399. 'carddata' => $cardData,
  400. ],
  401. [
  402. 'id' => 321,
  403. 'carddata' => $cardData,
  404. ],
  405. ]);
  406. $cards = $this->addressBook->getMultipleChildren(['Database:user1.vcf', 'LDAP:user2.vcf']);
  407. self::assertCount(2, $cards);
  408. }
  409. }