2121use OCP \AppFramework \Http \Attribute \NoCSRFRequired ;
2222use OCP \AppFramework \Http \Attribute \OpenAPI ;
2323use OCP \AppFramework \Http \Attribute \PasswordConfirmationRequired ;
24- use OCP \AppFramework \Http \Attribute \PublicPage ;
2524use OCP \AppFramework \Http \ContentSecurityPolicy ;
2625use OCP \AppFramework \Http \FileDisplayResponse ;
2726use OCP \AppFramework \Http \JSONResponse ;
4241use OCP \INavigationManager ;
4342use OCP \IRequest ;
4443use OCP \IURLGenerator ;
44+ use OCP \IUserSession ;
4545use OCP \L10N \IFactory ;
46+ use OCP \Security \RateLimiting \ILimiter ;
4647use OCP \Server ;
4748use OCP \Util ;
4849use Psr \Log \LoggerInterface ;
@@ -126,9 +127,8 @@ public function getAppDiscoverJSON(): JSONResponse {
126127 * @param string $image
127128 * @throws \Exception
128129 */
129- #[PublicPage]
130130 #[NoCSRFRequired]
131- public function getAppDiscoverMedia (string $ fileName ): Response {
131+ public function getAppDiscoverMedia (string $ fileName, ILimiter $ limiter , IUserSession $ session ): Response {
132132 $ getEtag = $ this ->discoverFetcher ->getETag () ?? date ('Y-m ' );
133133 $ etag = trim ($ getEtag , '" ' );
134134
@@ -158,6 +158,26 @@ public function getAppDiscoverMedia(string $fileName): Response {
158158 $ file = reset ($ file );
159159 // If not found request from Web
160160 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+
161181 try {
162182 $ client = $ this ->clientService ->newClient ();
163183 $ fileResponse = $ client ->get ($ fileName );
@@ -179,6 +199,31 @@ public function getAppDiscoverMedia(string $fileName): Response {
179199 return $ response ;
180200 }
181201
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+
182227 /**
183228 * Remove orphaned folders from the image cache that do not match the current etag
184229 * @param ISimpleFolder $folder The folder to clear
0 commit comments