Skip to content

Commit eb47e11

Browse files
committed
Avatars: Added redirect handling image fetching
Up to 3 times. Can be needed based upon testing with Auth0. Should be fine as long as it's something clearly documented. Added test to cover.
1 parent 9d6bc1a commit eb47e11

File tree

3 files changed

+37
-4
lines changed

3 files changed

+37
-4
lines changed

app/Config/oidc.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
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

app/Uploads/UserAvatars.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff 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]));

tests/Auth/OidcTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff 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([

0 commit comments

Comments
 (0)