SystemAddressBookTest.php 13 KB

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