23
23
use OCP \AppFramework \Http \Attribute \NoCSRFRequired ;
24
24
use OCP \AppFramework \Http \Attribute \OpenAPI ;
25
25
use OCP \AppFramework \Http \Attribute \PasswordConfirmationRequired ;
26
- use OCP \AppFramework \Http \Attribute \PublicPage ;
27
26
use OCP \AppFramework \Http \ContentSecurityPolicy ;
28
27
use OCP \AppFramework \Http \FileDisplayResponse ;
29
28
use OCP \AppFramework \Http \JSONResponse ;
43
42
use OCP \INavigationManager ;
44
43
use OCP \IRequest ;
45
44
use OCP \IURLGenerator ;
45
+ use OCP \IUserSession ;
46
46
use OCP \L10N \IFactory ;
47
+ use OCP \Security \RateLimiting \ILimiter ;
47
48
use OCP \Server ;
48
49
use Psr \Log \LoggerInterface ;
49
50
@@ -126,9 +127,8 @@ public function getAppDiscoverJSON(): JSONResponse {
126
127
* @param string $image
127
128
* @throws \Exception
128
129
*/
129
- #[PublicPage]
130
130
#[NoCSRFRequired]
131
- public function getAppDiscoverMedia (string $ fileName ): Response {
131
+ public function getAppDiscoverMedia (string $ fileName, ILimiter $ limiter , IUserSession $ session ): Response {
132
132
$ getEtag = $ this ->discoverFetcher ->getETag () ?? date ('Y-m ' );
133
133
$ etag = trim ($ getEtag , '" ' );
134
134
@@ -158,6 +158,26 @@ public function getAppDiscoverMedia(string $fileName): Response {
158
158
$ file = reset ($ file );
159
159
// If not found request from Web
160
160
if ($ file === false ) {
161
+ $ user = $ session ->getUser ();
162
+ // this route is not public thus we can assume a user is logged-in
163
+ assert ($ user !== null );
164
+ // Register a user request to throttle fetching external data
165
+ // this will prevent using the server for DoS of other systems.
166
+ $ limiter ->registerUserRequest (
167
+ 'settings-discover-media ' ,
168
+ // allow up to 24 media requests per hour
169
+ // this should be a sane default when a completely new section is loaded
170
+ // keep in mind browsers request all files from a source-set
171
+ 24 ,
172
+ 60 * 60 ,
173
+ $ user ,
174
+ );
175
+
176
+ if (!$ this ->checkCanDownloadMedia ($ fileName )) {
177
+ $ this ->logger ->warning ('Tried to load media files for app discover section from untrusted source ' );
178
+ return new NotFoundResponse (Http::STATUS_BAD_REQUEST );
179
+ }
180
+
161
181
try {
162
182
$ client = $ this ->clientService ->newClient ();
163
183
$ fileResponse = $ client ->get ($ fileName );
@@ -179,6 +199,31 @@ public function getAppDiscoverMedia(string $fileName): Response {
179
199
return $ response ;
180
200
}
181
201
202
+ private function checkCanDownloadMedia (string $ filename ): bool {
203
+ $ urlInfo = parse_url ($ filename );
204
+ if (!isset ($ urlInfo ['host ' ]) || !isset ($ urlInfo ['path ' ])) {
205
+ return false ;
206
+ }
207
+
208
+ // Always allowed hosts
209
+ if ($ urlInfo ['host ' ] === 'nextcloud.com ' ) {
210
+ return true ;
211
+ }
212
+
213
+ // Hosts that need further verification
214
+ // Github is only allowed if from our organization
215
+ $ ALLOWED_HOSTS = ['github.com ' , 'raw.githubusercontent.com ' ];
216
+ if (!in_array ($ urlInfo ['host ' ], $ ALLOWED_HOSTS )) {
217
+ return false ;
218
+ }
219
+
220
+ if (str_starts_with ($ urlInfo ['path ' ], '/nextcloud/ ' ) || str_starts_with ($ urlInfo ['path ' ], '/nextcloud-gmbh/ ' )) {
221
+ return true ;
222
+ }
223
+
224
+ return false ;
225
+ }
226
+
182
227
/**
183
228
* Remove orphaned folders from the image cache that do not match the current etag
184
229
* @param ISimpleFolder $folder The folder to clear
0 commit comments