File tree Expand file tree Collapse file tree 3 files changed +37
-4
lines changed
Expand file tree Collapse file tree 3 files changed +37
-4
lines changed Original file line number Diff line number Diff line change 4949
5050 // Enable fetching of the user's avatar from the 'picture' claim on login.
5151 // Will only be fetched if the user doesn't already have an avatar image assigned.
52- // This can be a security risk due to performing server-side fetching of data from external URLs.
53- // Only enable if you trust the OIDC auth provider to provide safe URLs for user images.
52+ // This can be a security risk due to performing server-side fetching (with up to 3 redirects) of
53+ // data from external URLs. Only enable if you trust the OIDC auth provider to provide safe URLs for user images.
5454 'fetch_avatar ' => env ('OIDC_FETCH_AVATAR ' , false ),
5555
5656 // Group sync options
Original file line number Diff line number Diff line change @@ -74,7 +74,7 @@ public function assignToUserFromUrl(User $user, string $avatarUrl): void
7474 $ user ->save ();
7575 } catch (Exception $ e ) {
7676 Log::error ('Failed to save user avatar image from URL ' , [
77- 'exception ' => $ e ,
77+ 'exception ' => $ e-> getMessage () ,
7878 'url ' => $ avatarUrl ,
7979 'user_id ' => $ user ->id ,
8080 ]);
@@ -141,7 +141,18 @@ protected function getAvatarImageData(string $url): string
141141 {
142142 try {
143143 $ client = $ this ->http ->buildClient (5 );
144- $ response = $ client ->sendRequest (new Request ('GET ' , $ url ));
144+ $ responseCount = 0 ;
145+
146+ do {
147+ $ response = $ client ->sendRequest (new Request ('GET ' , $ url ));
148+ $ responseCount ++;
149+ $ isRedirect = ($ response ->getStatusCode () === 301 || $ response ->getStatusCode () === 302 );
150+ $ url = $ response ->getHeader ('Location ' )[0 ] ?? '' ;
151+ } while ($ responseCount < 3 && $ isRedirect && is_string ($ url ) && str_starts_with ($ url , 'http ' ));
152+
153+ if ($ responseCount === 3 ) {
154+ throw new HttpFetchException ("Failed to fetch image, max redirect limit of 3 tries reached. Last fetched URL: {$ url }" );
155+ }
145156
146157 if ($ response ->getStatusCode () !== 200 ) {
147158 throw new HttpFetchException (trans ('errors.cannot_get_image_from_url ' , ['url ' => $ url ]));
Original file line number Diff line number Diff line change @@ -536,6 +536,28 @@ public function test_user_avatar_not_fetched_when_avatar_already_assigned()
536536 $ this ->assertEquals ($ originalImageData , $ newAvatarData );
537537 }
538538
539+ public function test_user_avatar_fetch_follows_up_to_three_redirects ()
540+ {
541+ config ()->set (['oidc.fetch_avatar ' => true ]);
542+
543+ $ logger = $ this ->withTestLogger ();
544+
545+ $ this ->runLogin ([
546+ 'email ' => 'avatar@example.com ' ,
547+ 'picture ' => 'https://example.com/my-avatar.jpg ' ,
548+ ], [
549+ new Response (302 , ['Location ' => 'https://example.com/a ' ]),
550+ new Response (302 , ['Location ' => 'https://example.com/b ' ]),
551+ new Response (302 , ['Location ' => 'https://example.com/c ' ]),
552+ new Response (302 , ['Location ' => 'https://example.com/d ' ]),
553+ ]);
554+
555+ $ user = User::query ()->where ('email ' , '= ' , 'avatar@example.com ' )->first ();
556+ $ this ->assertFalse ($ user ->avatar ()->exists ());
557+
558+ $ this ->assertStringContainsString ('"Failed to fetch image, max redirect limit of 3 tries reached. Last fetched URL: https://example.com/c" ' , $ logger ->getRecords ()[0 ]->formatted );
559+ }
560+
539561 public function test_login_group_sync ()
540562 {
541563 config ()->set ([
You can’t perform that action at this time.
0 commit comments