Completed
Push — master ( 3808f8...455fb7 )
by Julius
39:53 queued 15s
created
tests/lib/Federation/CloudIdTest.php 1 patch
Indentation   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -19,33 +19,33 @@
 block discarded – undo
19 19
  * @group DB
20 20
  */
21 21
 class CloudIdTest extends TestCase {
22
-	protected CloudIdManager&MockObject $cloudIdManager;
23
-
24
-	protected function setUp(): void {
25
-		parent::setUp();
26
-
27
-		$this->cloudIdManager = $this->createMock(CloudIdManager::class);
28
-		$this->overwriteService(ICloudIdManager::class, $this->cloudIdManager);
29
-	}
30
-
31
-	public function dataGetDisplayCloudId(): array {
32
-		return [
33
-			['[email protected]', 'test', 'example.com', '[email protected]'],
34
-			['test@http://example.com', 'test', 'http://example.com', '[email protected]'],
35
-			['test@https://example.com', 'test', 'https://example.com', '[email protected]'],
36
-			['test@https://example.com', 'test', 'https://example.com', 'Beloved [email protected]', 'Beloved Amy'],
37
-		];
38
-	}
39
-
40
-	/**
41
-	 * @dataProvider dataGetDisplayCloudId
42
-	 */
43
-	public function testGetDisplayCloudId(string $id, string $user, string $remote, string $display, ?string $addressbookName = null): void {
44
-		$this->cloudIdManager->expects($this->once())
45
-			->method('getDisplayNameFromContact')
46
-			->willReturn($addressbookName);
47
-
48
-		$cloudId = new CloudId($id, $user, $remote);
49
-		$this->assertEquals($display, $cloudId->getDisplayId());
50
-	}
22
+    protected CloudIdManager&MockObject $cloudIdManager;
23
+
24
+    protected function setUp(): void {
25
+        parent::setUp();
26
+
27
+        $this->cloudIdManager = $this->createMock(CloudIdManager::class);
28
+        $this->overwriteService(ICloudIdManager::class, $this->cloudIdManager);
29
+    }
30
+
31
+    public function dataGetDisplayCloudId(): array {
32
+        return [
33
+            ['[email protected]', 'test', 'example.com', '[email protected]'],
34
+            ['test@http://example.com', 'test', 'http://example.com', '[email protected]'],
35
+            ['test@https://example.com', 'test', 'https://example.com', '[email protected]'],
36
+            ['test@https://example.com', 'test', 'https://example.com', 'Beloved [email protected]', 'Beloved Amy'],
37
+        ];
38
+    }
39
+
40
+    /**
41
+     * @dataProvider dataGetDisplayCloudId
42
+     */
43
+    public function testGetDisplayCloudId(string $id, string $user, string $remote, string $display, ?string $addressbookName = null): void {
44
+        $this->cloudIdManager->expects($this->once())
45
+            ->method('getDisplayNameFromContact')
46
+            ->willReturn($addressbookName);
47
+
48
+        $cloudId = new CloudId($id, $user, $remote);
49
+        $this->assertEquals($display, $cloudId->getDisplayId());
50
+    }
51 51
 }
Please login to merge, or discard this patch.
tests/lib/Federation/CloudIdManagerTest.php 1 patch
Indentation   +125 added lines, -125 removed lines patch added patch discarded remove patch
@@ -23,129 +23,129 @@
 block discarded – undo
23 23
  * @group DB
24 24
  */
25 25
 class CloudIdManagerTest extends TestCase {
26
-	/** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
27
-	protected $contactsManager;
28
-	/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
29
-	private $urlGenerator;
30
-	/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
31
-	private $userManager;
32
-	/** @var CloudIdManager */
33
-	private $cloudIdManager;
34
-	/** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */
35
-	private $cacheFactory;
36
-
37
-
38
-	protected function setUp(): void {
39
-		parent::setUp();
40
-
41
-		$this->contactsManager = $this->createMock(IManager::class);
42
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
43
-		$this->userManager = $this->createMock(IUserManager::class);
44
-
45
-		$this->cacheFactory = $this->createMock(ICacheFactory::class);
46
-		$this->cacheFactory->method('createDistributed')
47
-			->willReturn(new ArrayCache(''));
48
-
49
-		$this->cloudIdManager = new CloudIdManager(
50
-			$this->contactsManager,
51
-			$this->urlGenerator,
52
-			$this->userManager,
53
-			$this->cacheFactory,
54
-			$this->createMock(IEventDispatcher::class)
55
-		);
56
-		$this->overwriteService(ICloudIdManager::class, $this->cloudIdManager);
57
-	}
58
-
59
-	public function cloudIdProvider(): array {
60
-		return [
61
-			['[email protected]', 'test', 'example.com', '[email protected]'],
62
-			['[email protected]/cloud', 'test', 'example.com/cloud', '[email protected]/cloud'],
63
-			['[email protected]/cloud/', 'test', 'example.com/cloud', '[email protected]/cloud'],
64
-			['[email protected]/cloud/index.php', 'test', 'example.com/cloud', '[email protected]/cloud'],
65
-			['[email protected]@example.com', '[email protected]', 'example.com', '[email protected]@example.com'],
66
-		];
67
-	}
68
-
69
-	/**
70
-	 * @dataProvider cloudIdProvider
71
-	 */
72
-	public function testResolveCloudId(string $cloudId, string $user, string $noProtocolRemote, string $cleanId): void {
73
-		$displayName = 'Ample Ex';
74
-
75
-		$this->contactsManager->expects($this->any())
76
-			->method('search')
77
-			->with($cleanId, ['CLOUD'])
78
-			->willReturn([
79
-				[
80
-					'CLOUD' => [$cleanId],
81
-					'FN' => $displayName,
82
-				]
83
-			]);
84
-
85
-		$cloudId = $this->cloudIdManager->resolveCloudId($cloudId);
86
-
87
-		$this->assertEquals($user, $cloudId->getUser());
88
-		$this->assertEquals('https://' . $noProtocolRemote, $cloudId->getRemote());
89
-		$this->assertEquals($cleanId, $cloudId->getId());
90
-		$this->assertEquals($displayName . '@' . $noProtocolRemote, $cloudId->getDisplayId());
91
-	}
92
-
93
-	public function invalidCloudIdProvider(): array {
94
-		return [
95
-			['example.com'],
96
-			['test:[email protected]'],
97
-			['test/[email protected]']
98
-		];
99
-	}
100
-
101
-	/**
102
-	 * @dataProvider invalidCloudIdProvider
103
-	 */
104
-	public function testInvalidCloudId(string $cloudId): void {
105
-		$this->expectException(\InvalidArgumentException::class);
106
-
107
-		$this->contactsManager->expects($this->never())
108
-			->method('search');
109
-
110
-		$this->cloudIdManager->resolveCloudId($cloudId);
111
-	}
112
-
113
-	public function getCloudIdProvider(): array {
114
-		return [
115
-			['test', 'example.com', '[email protected]', null, 'https://example.com', 'https://example.com'],
116
-			['test', 'http://example.com', 'test@http://example.com', '[email protected]'],
117
-			['test', null, 'test@http://example.com', '[email protected]', 'http://example.com', 'http://example.com'],
118
-			['[email protected]', 'example.com', '[email protected]@example.com', null, 'https://example.com', 'https://example.com'],
119
-			['[email protected]', 'https://example.com', '[email protected]@example.com'],
120
-			['[email protected]', null, '[email protected]@example.com', null, 'https://example.com', 'https://example.com'],
121
-			['[email protected]', 'https://example.com/index.php/s/shareToken', '[email protected]@example.com', null, 'https://example.com', 'https://example.com'],
122
-		];
123
-	}
124
-
125
-	/**
126
-	 * @dataProvider getCloudIdProvider
127
-	 */
128
-	public function testGetCloudId(string $user, ?string $remote, string $id, ?string $searchCloudId = null, ?string $localHost = 'https://example.com', ?string $expectedRemoteId = null): void {
129
-		if ($remote !== null) {
130
-			$this->contactsManager->expects($this->any())
131
-				->method('search')
132
-				->with($searchCloudId ?? $id, ['CLOUD'])
133
-				->willReturn([
134
-					[
135
-						'CLOUD' => [$searchCloudId ?? $id],
136
-						'FN' => 'Ample Ex',
137
-					]
138
-				]);
139
-		} else {
140
-			$this->urlGenerator->expects(self::once())
141
-				->method('getAbsoluteUrl')
142
-				->willReturn($localHost);
143
-		}
144
-		$expectedRemoteId ??= $remote;
145
-
146
-		$cloudId = $this->cloudIdManager->getCloudId($user, $remote);
147
-
148
-		$this->assertEquals($id, $cloudId->getId(), 'Cloud ID');
149
-		$this->assertEquals($expectedRemoteId, $cloudId->getRemote(), 'Remote URL');
150
-	}
26
+    /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
27
+    protected $contactsManager;
28
+    /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
29
+    private $urlGenerator;
30
+    /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
31
+    private $userManager;
32
+    /** @var CloudIdManager */
33
+    private $cloudIdManager;
34
+    /** @var ICacheFactory|\PHPUnit\Framework\MockObject\MockObject */
35
+    private $cacheFactory;
36
+
37
+
38
+    protected function setUp(): void {
39
+        parent::setUp();
40
+
41
+        $this->contactsManager = $this->createMock(IManager::class);
42
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
43
+        $this->userManager = $this->createMock(IUserManager::class);
44
+
45
+        $this->cacheFactory = $this->createMock(ICacheFactory::class);
46
+        $this->cacheFactory->method('createDistributed')
47
+            ->willReturn(new ArrayCache(''));
48
+
49
+        $this->cloudIdManager = new CloudIdManager(
50
+            $this->contactsManager,
51
+            $this->urlGenerator,
52
+            $this->userManager,
53
+            $this->cacheFactory,
54
+            $this->createMock(IEventDispatcher::class)
55
+        );
56
+        $this->overwriteService(ICloudIdManager::class, $this->cloudIdManager);
57
+    }
58
+
59
+    public function cloudIdProvider(): array {
60
+        return [
61
+            ['[email protected]', 'test', 'example.com', '[email protected]'],
62
+            ['[email protected]/cloud', 'test', 'example.com/cloud', '[email protected]/cloud'],
63
+            ['[email protected]/cloud/', 'test', 'example.com/cloud', '[email protected]/cloud'],
64
+            ['[email protected]/cloud/index.php', 'test', 'example.com/cloud', '[email protected]/cloud'],
65
+            ['[email protected]@example.com', '[email protected]', 'example.com', '[email protected]@example.com'],
66
+        ];
67
+    }
68
+
69
+    /**
70
+     * @dataProvider cloudIdProvider
71
+     */
72
+    public function testResolveCloudId(string $cloudId, string $user, string $noProtocolRemote, string $cleanId): void {
73
+        $displayName = 'Ample Ex';
74
+
75
+        $this->contactsManager->expects($this->any())
76
+            ->method('search')
77
+            ->with($cleanId, ['CLOUD'])
78
+            ->willReturn([
79
+                [
80
+                    'CLOUD' => [$cleanId],
81
+                    'FN' => $displayName,
82
+                ]
83
+            ]);
84
+
85
+        $cloudId = $this->cloudIdManager->resolveCloudId($cloudId);
86
+
87
+        $this->assertEquals($user, $cloudId->getUser());
88
+        $this->assertEquals('https://' . $noProtocolRemote, $cloudId->getRemote());
89
+        $this->assertEquals($cleanId, $cloudId->getId());
90
+        $this->assertEquals($displayName . '@' . $noProtocolRemote, $cloudId->getDisplayId());
91
+    }
92
+
93
+    public function invalidCloudIdProvider(): array {
94
+        return [
95
+            ['example.com'],
96
+            ['test:[email protected]'],
97
+            ['test/[email protected]']
98
+        ];
99
+    }
100
+
101
+    /**
102
+     * @dataProvider invalidCloudIdProvider
103
+     */
104
+    public function testInvalidCloudId(string $cloudId): void {
105
+        $this->expectException(\InvalidArgumentException::class);
106
+
107
+        $this->contactsManager->expects($this->never())
108
+            ->method('search');
109
+
110
+        $this->cloudIdManager->resolveCloudId($cloudId);
111
+    }
112
+
113
+    public function getCloudIdProvider(): array {
114
+        return [
115
+            ['test', 'example.com', '[email protected]', null, 'https://example.com', 'https://example.com'],
116
+            ['test', 'http://example.com', 'test@http://example.com', '[email protected]'],
117
+            ['test', null, 'test@http://example.com', '[email protected]', 'http://example.com', 'http://example.com'],
118
+            ['[email protected]', 'example.com', '[email protected]@example.com', null, 'https://example.com', 'https://example.com'],
119
+            ['[email protected]', 'https://example.com', '[email protected]@example.com'],
120
+            ['[email protected]', null, '[email protected]@example.com', null, 'https://example.com', 'https://example.com'],
121
+            ['[email protected]', 'https://example.com/index.php/s/shareToken', '[email protected]@example.com', null, 'https://example.com', 'https://example.com'],
122
+        ];
123
+    }
124
+
125
+    /**
126
+     * @dataProvider getCloudIdProvider
127
+     */
128
+    public function testGetCloudId(string $user, ?string $remote, string $id, ?string $searchCloudId = null, ?string $localHost = 'https://example.com', ?string $expectedRemoteId = null): void {
129
+        if ($remote !== null) {
130
+            $this->contactsManager->expects($this->any())
131
+                ->method('search')
132
+                ->with($searchCloudId ?? $id, ['CLOUD'])
133
+                ->willReturn([
134
+                    [
135
+                        'CLOUD' => [$searchCloudId ?? $id],
136
+                        'FN' => 'Ample Ex',
137
+                    ]
138
+                ]);
139
+        } else {
140
+            $this->urlGenerator->expects(self::once())
141
+                ->method('getAbsoluteUrl')
142
+                ->willReturn($localHost);
143
+        }
144
+        $expectedRemoteId ??= $remote;
145
+
146
+        $cloudId = $this->cloudIdManager->getCloudId($user, $remote);
147
+
148
+        $this->assertEquals($id, $cloudId->getId(), 'Cloud ID');
149
+        $this->assertEquals($expectedRemoteId, $cloudId->getRemote(), 'Remote URL');
150
+    }
151 151
 }
Please login to merge, or discard this patch.
lib/private/Federation/CloudIdManager.php 2 patches
Indentation   +226 added lines, -226 removed lines patch added patch discarded remove patch
@@ -21,230 +21,230 @@
 block discarded – undo
21 21
 use OCP\User\Events\UserChangedEvent;
22 22
 
23 23
 class CloudIdManager implements ICloudIdManager {
24
-	/** @var IManager */
25
-	private $contactsManager;
26
-	/** @var IURLGenerator */
27
-	private $urlGenerator;
28
-	/** @var IUserManager */
29
-	private $userManager;
30
-	private ICache $memCache;
31
-	private ICache $displayNameCache;
32
-	/** @var array[] */
33
-	private array $cache = [];
34
-
35
-	public function __construct(
36
-		IManager $contactsManager,
37
-		IURLGenerator $urlGenerator,
38
-		IUserManager $userManager,
39
-		ICacheFactory $cacheFactory,
40
-		IEventDispatcher $eventDispatcher,
41
-	) {
42
-		$this->contactsManager = $contactsManager;
43
-		$this->urlGenerator = $urlGenerator;
44
-		$this->userManager = $userManager;
45
-		$this->memCache = $cacheFactory->createDistributed('cloud_id_');
46
-		$this->displayNameCache = $cacheFactory->createDistributed('cloudid_name_');
47
-		$eventDispatcher->addListener(UserChangedEvent::class, [$this, 'handleUserEvent']);
48
-		$eventDispatcher->addListener(CardUpdatedEvent::class, [$this, 'handleCardEvent']);
49
-	}
50
-
51
-	public function handleUserEvent(Event $event): void {
52
-		if ($event instanceof UserChangedEvent && $event->getFeature() === 'displayName') {
53
-			$userId = $event->getUser()->getUID();
54
-			$key = $userId . '@local';
55
-			unset($this->cache[$key]);
56
-			$this->memCache->remove($key);
57
-		}
58
-	}
59
-
60
-	public function handleCardEvent(Event $event): void {
61
-		if ($event instanceof CardUpdatedEvent) {
62
-			$data = $event->getCardData()['carddata'];
63
-			foreach (explode("\r\n", $data) as $line) {
64
-				if (str_starts_with($line, 'CLOUD;')) {
65
-					$parts = explode(':', $line, 2);
66
-					if (isset($parts[1])) {
67
-						$key = $parts[1];
68
-						unset($this->cache[$key]);
69
-						$this->memCache->remove($key);
70
-					}
71
-				}
72
-			}
73
-		}
74
-	}
75
-
76
-	/**
77
-	 * @param string $cloudId
78
-	 * @return ICloudId
79
-	 * @throws \InvalidArgumentException
80
-	 */
81
-	public function resolveCloudId(string $cloudId): ICloudId {
82
-		// TODO magic here to get the url and user instead of just splitting on @
83
-
84
-		if (!$this->isValidCloudId($cloudId)) {
85
-			throw new \InvalidArgumentException('Invalid cloud id');
86
-		}
87
-
88
-		// Find the first character that is not allowed in user names
89
-		$id = $this->stripShareLinkFragments($cloudId);
90
-		$posSlash = strpos($id, '/');
91
-		$posColon = strpos($id, ':');
92
-
93
-		if ($posSlash === false && $posColon === false) {
94
-			$invalidPos = \strlen($id);
95
-		} elseif ($posSlash === false) {
96
-			$invalidPos = $posColon;
97
-		} elseif ($posColon === false) {
98
-			$invalidPos = $posSlash;
99
-		} else {
100
-			$invalidPos = min($posSlash, $posColon);
101
-		}
102
-
103
-		$lastValidAtPos = strrpos($id, '@', $invalidPos - strlen($id));
104
-
105
-		if ($lastValidAtPos !== false) {
106
-			$user = substr($id, 0, $lastValidAtPos);
107
-			$remote = substr($id, $lastValidAtPos + 1);
108
-
109
-			$this->userManager->validateUserId($user);
110
-
111
-			if (!empty($user) && !empty($remote)) {
112
-				$remote = $this->ensureDefaultProtocol($remote);
113
-				return new CloudId($id, $user, $remote, null);
114
-			}
115
-		}
116
-		throw new \InvalidArgumentException('Invalid cloud id');
117
-	}
118
-
119
-	public function getDisplayNameFromContact(string $cloudId): ?string {
120
-		$cachedName = $this->displayNameCache->get($cloudId);
121
-		if ($cachedName !== null) {
122
-			return $cachedName;
123
-		}
124
-
125
-		$addressBookEntries = $this->contactsManager->search($cloudId, ['CLOUD'], [
126
-			'limit' => 1,
127
-			'enumeration' => false,
128
-			'fullmatch' => false,
129
-			'strict_search' => true,
130
-		]);
131
-		foreach ($addressBookEntries as $entry) {
132
-			if (isset($entry['CLOUD'])) {
133
-				foreach ($entry['CLOUD'] as $cloudID) {
134
-					if ($cloudID === $cloudId) {
135
-						// Warning, if user decides to make their full name local only,
136
-						// no FN is found on federated servers
137
-						if (isset($entry['FN'])) {
138
-							$this->displayNameCache->set($cloudId, $entry['FN'], 15 * 60);
139
-							return $entry['FN'];
140
-						} else {
141
-							$this->displayNameCache->set($cloudId, $cloudID, 15 * 60);
142
-							return $cloudID;
143
-						}
144
-					}
145
-				}
146
-			}
147
-		}
148
-		$this->displayNameCache->set($cloudId, $cloudId, 15 * 60);
149
-		return null;
150
-	}
151
-
152
-	/**
153
-	 * @param string $user
154
-	 * @param string|null $remote
155
-	 * @return CloudId
156
-	 */
157
-	public function getCloudId(string $user, ?string $remote): ICloudId {
158
-		$isLocal = $remote === null;
159
-		if ($isLocal) {
160
-			$remote = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
161
-		}
162
-
163
-		// note that for remote id's we don't strip the protocol for the remote we use to construct the CloudId
164
-		// this way if a user has an explicit non-https cloud id this will be preserved
165
-		// we do still use the version without protocol for looking up the display name
166
-		$remote = $this->stripShareLinkFragments($remote);
167
-		$host = $this->removeProtocolFromUrl($remote);
168
-		$remote = $this->ensureDefaultProtocol($remote);
169
-
170
-		$key = $user . '@' . ($isLocal ? 'local' : $host);
171
-		$cached = $this->cache[$key] ?? $this->memCache->get($key);
172
-		if ($cached) {
173
-			$this->cache[$key] = $cached; // put items from memcache into local cache
174
-			return new CloudId($cached['id'], $cached['user'], $cached['remote'], $cached['displayName']);
175
-		}
176
-
177
-		if ($isLocal) {
178
-			$localUser = $this->userManager->get($user);
179
-			$displayName = $localUser ? $localUser->getDisplayName() : '';
180
-		} else {
181
-			$displayName = null;
182
-		}
183
-
184
-		// For the visible cloudID we only strip away https
185
-		$id = $user . '@' . $this->removeProtocolFromUrl($remote, true);
186
-
187
-		$data = [
188
-			'id' => $id,
189
-			'user' => $user,
190
-			'remote' => $remote,
191
-			'displayName' => $displayName,
192
-		];
193
-		$this->cache[$key] = $data;
194
-		$this->memCache->set($key, $data, 15 * 60);
195
-		return new CloudId($id, $user, $remote, $displayName);
196
-	}
197
-
198
-	/**
199
-	 * @param string $url
200
-	 * @return string
201
-	 */
202
-	public function removeProtocolFromUrl(string $url, bool $httpsOnly = false): string {
203
-		if (str_starts_with($url, 'https://')) {
204
-			return substr($url, 8);
205
-		}
206
-		if (!$httpsOnly && str_starts_with($url, 'http://')) {
207
-			return substr($url, 7);
208
-		}
209
-
210
-		return $url;
211
-	}
212
-
213
-	protected function ensureDefaultProtocol(string $remote): string {
214
-		if (!str_contains($remote, '://')) {
215
-			$remote = 'https://' . $remote;
216
-		}
217
-
218
-		return $remote;
219
-	}
220
-
221
-	/**
222
-	 * Strips away a potential file names and trailing slashes:
223
-	 * - http://localhost
224
-	 * - http://localhost/
225
-	 * - http://localhost/index.php
226
-	 * - http://localhost/index.php/s/{shareToken}
227
-	 *
228
-	 * all return: http://localhost
229
-	 *
230
-	 * @param string $remote
231
-	 * @return string
232
-	 */
233
-	protected function stripShareLinkFragments(string $remote): string {
234
-		$remote = str_replace('\\', '/', $remote);
235
-		if ($fileNamePosition = strpos($remote, '/index.php')) {
236
-			$remote = substr($remote, 0, $fileNamePosition);
237
-		}
238
-		$remote = rtrim($remote, '/');
239
-
240
-		return $remote;
241
-	}
242
-
243
-	/**
244
-	 * @param string $cloudId
245
-	 * @return bool
246
-	 */
247
-	public function isValidCloudId(string $cloudId): bool {
248
-		return str_contains($cloudId, '@');
249
-	}
24
+    /** @var IManager */
25
+    private $contactsManager;
26
+    /** @var IURLGenerator */
27
+    private $urlGenerator;
28
+    /** @var IUserManager */
29
+    private $userManager;
30
+    private ICache $memCache;
31
+    private ICache $displayNameCache;
32
+    /** @var array[] */
33
+    private array $cache = [];
34
+
35
+    public function __construct(
36
+        IManager $contactsManager,
37
+        IURLGenerator $urlGenerator,
38
+        IUserManager $userManager,
39
+        ICacheFactory $cacheFactory,
40
+        IEventDispatcher $eventDispatcher,
41
+    ) {
42
+        $this->contactsManager = $contactsManager;
43
+        $this->urlGenerator = $urlGenerator;
44
+        $this->userManager = $userManager;
45
+        $this->memCache = $cacheFactory->createDistributed('cloud_id_');
46
+        $this->displayNameCache = $cacheFactory->createDistributed('cloudid_name_');
47
+        $eventDispatcher->addListener(UserChangedEvent::class, [$this, 'handleUserEvent']);
48
+        $eventDispatcher->addListener(CardUpdatedEvent::class, [$this, 'handleCardEvent']);
49
+    }
50
+
51
+    public function handleUserEvent(Event $event): void {
52
+        if ($event instanceof UserChangedEvent && $event->getFeature() === 'displayName') {
53
+            $userId = $event->getUser()->getUID();
54
+            $key = $userId . '@local';
55
+            unset($this->cache[$key]);
56
+            $this->memCache->remove($key);
57
+        }
58
+    }
59
+
60
+    public function handleCardEvent(Event $event): void {
61
+        if ($event instanceof CardUpdatedEvent) {
62
+            $data = $event->getCardData()['carddata'];
63
+            foreach (explode("\r\n", $data) as $line) {
64
+                if (str_starts_with($line, 'CLOUD;')) {
65
+                    $parts = explode(':', $line, 2);
66
+                    if (isset($parts[1])) {
67
+                        $key = $parts[1];
68
+                        unset($this->cache[$key]);
69
+                        $this->memCache->remove($key);
70
+                    }
71
+                }
72
+            }
73
+        }
74
+    }
75
+
76
+    /**
77
+     * @param string $cloudId
78
+     * @return ICloudId
79
+     * @throws \InvalidArgumentException
80
+     */
81
+    public function resolveCloudId(string $cloudId): ICloudId {
82
+        // TODO magic here to get the url and user instead of just splitting on @
83
+
84
+        if (!$this->isValidCloudId($cloudId)) {
85
+            throw new \InvalidArgumentException('Invalid cloud id');
86
+        }
87
+
88
+        // Find the first character that is not allowed in user names
89
+        $id = $this->stripShareLinkFragments($cloudId);
90
+        $posSlash = strpos($id, '/');
91
+        $posColon = strpos($id, ':');
92
+
93
+        if ($posSlash === false && $posColon === false) {
94
+            $invalidPos = \strlen($id);
95
+        } elseif ($posSlash === false) {
96
+            $invalidPos = $posColon;
97
+        } elseif ($posColon === false) {
98
+            $invalidPos = $posSlash;
99
+        } else {
100
+            $invalidPos = min($posSlash, $posColon);
101
+        }
102
+
103
+        $lastValidAtPos = strrpos($id, '@', $invalidPos - strlen($id));
104
+
105
+        if ($lastValidAtPos !== false) {
106
+            $user = substr($id, 0, $lastValidAtPos);
107
+            $remote = substr($id, $lastValidAtPos + 1);
108
+
109
+            $this->userManager->validateUserId($user);
110
+
111
+            if (!empty($user) && !empty($remote)) {
112
+                $remote = $this->ensureDefaultProtocol($remote);
113
+                return new CloudId($id, $user, $remote, null);
114
+            }
115
+        }
116
+        throw new \InvalidArgumentException('Invalid cloud id');
117
+    }
118
+
119
+    public function getDisplayNameFromContact(string $cloudId): ?string {
120
+        $cachedName = $this->displayNameCache->get($cloudId);
121
+        if ($cachedName !== null) {
122
+            return $cachedName;
123
+        }
124
+
125
+        $addressBookEntries = $this->contactsManager->search($cloudId, ['CLOUD'], [
126
+            'limit' => 1,
127
+            'enumeration' => false,
128
+            'fullmatch' => false,
129
+            'strict_search' => true,
130
+        ]);
131
+        foreach ($addressBookEntries as $entry) {
132
+            if (isset($entry['CLOUD'])) {
133
+                foreach ($entry['CLOUD'] as $cloudID) {
134
+                    if ($cloudID === $cloudId) {
135
+                        // Warning, if user decides to make their full name local only,
136
+                        // no FN is found on federated servers
137
+                        if (isset($entry['FN'])) {
138
+                            $this->displayNameCache->set($cloudId, $entry['FN'], 15 * 60);
139
+                            return $entry['FN'];
140
+                        } else {
141
+                            $this->displayNameCache->set($cloudId, $cloudID, 15 * 60);
142
+                            return $cloudID;
143
+                        }
144
+                    }
145
+                }
146
+            }
147
+        }
148
+        $this->displayNameCache->set($cloudId, $cloudId, 15 * 60);
149
+        return null;
150
+    }
151
+
152
+    /**
153
+     * @param string $user
154
+     * @param string|null $remote
155
+     * @return CloudId
156
+     */
157
+    public function getCloudId(string $user, ?string $remote): ICloudId {
158
+        $isLocal = $remote === null;
159
+        if ($isLocal) {
160
+            $remote = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
161
+        }
162
+
163
+        // note that for remote id's we don't strip the protocol for the remote we use to construct the CloudId
164
+        // this way if a user has an explicit non-https cloud id this will be preserved
165
+        // we do still use the version without protocol for looking up the display name
166
+        $remote = $this->stripShareLinkFragments($remote);
167
+        $host = $this->removeProtocolFromUrl($remote);
168
+        $remote = $this->ensureDefaultProtocol($remote);
169
+
170
+        $key = $user . '@' . ($isLocal ? 'local' : $host);
171
+        $cached = $this->cache[$key] ?? $this->memCache->get($key);
172
+        if ($cached) {
173
+            $this->cache[$key] = $cached; // put items from memcache into local cache
174
+            return new CloudId($cached['id'], $cached['user'], $cached['remote'], $cached['displayName']);
175
+        }
176
+
177
+        if ($isLocal) {
178
+            $localUser = $this->userManager->get($user);
179
+            $displayName = $localUser ? $localUser->getDisplayName() : '';
180
+        } else {
181
+            $displayName = null;
182
+        }
183
+
184
+        // For the visible cloudID we only strip away https
185
+        $id = $user . '@' . $this->removeProtocolFromUrl($remote, true);
186
+
187
+        $data = [
188
+            'id' => $id,
189
+            'user' => $user,
190
+            'remote' => $remote,
191
+            'displayName' => $displayName,
192
+        ];
193
+        $this->cache[$key] = $data;
194
+        $this->memCache->set($key, $data, 15 * 60);
195
+        return new CloudId($id, $user, $remote, $displayName);
196
+    }
197
+
198
+    /**
199
+     * @param string $url
200
+     * @return string
201
+     */
202
+    public function removeProtocolFromUrl(string $url, bool $httpsOnly = false): string {
203
+        if (str_starts_with($url, 'https://')) {
204
+            return substr($url, 8);
205
+        }
206
+        if (!$httpsOnly && str_starts_with($url, 'http://')) {
207
+            return substr($url, 7);
208
+        }
209
+
210
+        return $url;
211
+    }
212
+
213
+    protected function ensureDefaultProtocol(string $remote): string {
214
+        if (!str_contains($remote, '://')) {
215
+            $remote = 'https://' . $remote;
216
+        }
217
+
218
+        return $remote;
219
+    }
220
+
221
+    /**
222
+     * Strips away a potential file names and trailing slashes:
223
+     * - http://localhost
224
+     * - http://localhost/
225
+     * - http://localhost/index.php
226
+     * - http://localhost/index.php/s/{shareToken}
227
+     *
228
+     * all return: http://localhost
229
+     *
230
+     * @param string $remote
231
+     * @return string
232
+     */
233
+    protected function stripShareLinkFragments(string $remote): string {
234
+        $remote = str_replace('\\', '/', $remote);
235
+        if ($fileNamePosition = strpos($remote, '/index.php')) {
236
+            $remote = substr($remote, 0, $fileNamePosition);
237
+        }
238
+        $remote = rtrim($remote, '/');
239
+
240
+        return $remote;
241
+    }
242
+
243
+    /**
244
+     * @param string $cloudId
245
+     * @return bool
246
+     */
247
+    public function isValidCloudId(string $cloudId): bool {
248
+        return str_contains($cloudId, '@');
249
+    }
250 250
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -51,7 +51,7 @@  discard block
 block discarded – undo
51 51
 	public function handleUserEvent(Event $event): void {
52 52
 		if ($event instanceof UserChangedEvent && $event->getFeature() === 'displayName') {
53 53
 			$userId = $event->getUser()->getUID();
54
-			$key = $userId . '@local';
54
+			$key = $userId.'@local';
55 55
 			unset($this->cache[$key]);
56 56
 			$this->memCache->remove($key);
57 57
 		}
@@ -167,7 +167,7 @@  discard block
 block discarded – undo
167 167
 		$host = $this->removeProtocolFromUrl($remote);
168 168
 		$remote = $this->ensureDefaultProtocol($remote);
169 169
 
170
-		$key = $user . '@' . ($isLocal ? 'local' : $host);
170
+		$key = $user.'@'.($isLocal ? 'local' : $host);
171 171
 		$cached = $this->cache[$key] ?? $this->memCache->get($key);
172 172
 		if ($cached) {
173 173
 			$this->cache[$key] = $cached; // put items from memcache into local cache
@@ -182,7 +182,7 @@  discard block
 block discarded – undo
182 182
 		}
183 183
 
184 184
 		// For the visible cloudID we only strip away https
185
-		$id = $user . '@' . $this->removeProtocolFromUrl($remote, true);
185
+		$id = $user.'@'.$this->removeProtocolFromUrl($remote, true);
186 186
 
187 187
 		$data = [
188 188
 			'id' => $id,
@@ -212,7 +212,7 @@  discard block
 block discarded – undo
212 212
 
213 213
 	protected function ensureDefaultProtocol(string $remote): string {
214 214
 		if (!str_contains($remote, '://')) {
215
-			$remote = 'https://' . $remote;
215
+			$remote = 'https://'.$remote;
216 216
 		}
217 217
 
218 218
 		return $remote;
Please login to merge, or discard this patch.
lib/private/Federation/CloudId.php 2 patches
Indentation   +43 added lines, -43 removed lines patch added patch discarded remove patch
@@ -12,53 +12,53 @@
 block discarded – undo
12 12
 use OCP\Federation\ICloudIdManager;
13 13
 
14 14
 class CloudId implements ICloudId {
15
-	public function __construct(
16
-		protected string $id,
17
-		protected string $user,
18
-		protected string $remote,
19
-		protected ?string $displayName = null,
20
-	) {
21
-	}
15
+    public function __construct(
16
+        protected string $id,
17
+        protected string $user,
18
+        protected string $remote,
19
+        protected ?string $displayName = null,
20
+    ) {
21
+    }
22 22
 
23
-	/**
24
-	 * The full remote cloud id
25
-	 *
26
-	 * @return string
27
-	 */
28
-	public function getId(): string {
29
-		return $this->id;
30
-	}
23
+    /**
24
+     * The full remote cloud id
25
+     *
26
+     * @return string
27
+     */
28
+    public function getId(): string {
29
+        return $this->id;
30
+    }
31 31
 
32
-	public function getDisplayId(): string {
33
-		if ($this->displayName === null) {
34
-			/** @var CloudIdManager $cloudIdManager */
35
-			$cloudIdManager = \OCP\Server::get(ICloudIdManager::class);
36
-			$this->displayName = $cloudIdManager->getDisplayNameFromContact($this->getId());
37
-		}
32
+    public function getDisplayId(): string {
33
+        if ($this->displayName === null) {
34
+            /** @var CloudIdManager $cloudIdManager */
35
+            $cloudIdManager = \OCP\Server::get(ICloudIdManager::class);
36
+            $this->displayName = $cloudIdManager->getDisplayNameFromContact($this->getId());
37
+        }
38 38
 
39
-		$atHost = str_replace(['http://', 'https://'], '', $this->getRemote());
39
+        $atHost = str_replace(['http://', 'https://'], '', $this->getRemote());
40 40
 
41
-		if ($this->displayName) {
42
-			return $this->displayName . '@' . $atHost;
43
-		}
44
-		return $this->getUser() . '@' . $atHost;
45
-	}
41
+        if ($this->displayName) {
42
+            return $this->displayName . '@' . $atHost;
43
+        }
44
+        return $this->getUser() . '@' . $atHost;
45
+    }
46 46
 
47
-	/**
48
-	 * The username on the remote server
49
-	 *
50
-	 * @return string
51
-	 */
52
-	public function getUser(): string {
53
-		return $this->user;
54
-	}
47
+    /**
48
+     * The username on the remote server
49
+     *
50
+     * @return string
51
+     */
52
+    public function getUser(): string {
53
+        return $this->user;
54
+    }
55 55
 
56
-	/**
57
-	 * The base address of the remote server
58
-	 *
59
-	 * @return string
60
-	 */
61
-	public function getRemote(): string {
62
-		return $this->remote;
63
-	}
56
+    /**
57
+     * The base address of the remote server
58
+     *
59
+     * @return string
60
+     */
61
+    public function getRemote(): string {
62
+        return $this->remote;
63
+    }
64 64
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -39,9 +39,9 @@
 block discarded – undo
39 39
 		$atHost = str_replace(['http://', 'https://'], '', $this->getRemote());
40 40
 
41 41
 		if ($this->displayName) {
42
-			return $this->displayName . '@' . $atHost;
42
+			return $this->displayName.'@'.$atHost;
43 43
 		}
44
-		return $this->getUser() . '@' . $atHost;
44
+		return $this->getUser().'@'.$atHost;
45 45
 	}
46 46
 
47 47
 	/**
Please login to merge, or discard this patch.