Skip to content

Commit 6c8f2f2

Browse files
fix(functions): Refresh credentials before enqueueing task
This change addresses an issue where enqueueing a task from a Cloud Function would fail with a InvalidArgumentError error. This was caused by uninitialized credentials being used to in the task payload. The fix explicitly refreshes the credential before accessing the credential, ensuring a valid token or service account email is used in the in the task payload. This also includes a correction for an f-string typo in the Authorization header construction.
1 parent 5e75250 commit 6c8f2f2

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

firebase_admin/functions.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
from base64 import b64encode
2323
from typing import Any, Optional, Dict
2424
from dataclasses import dataclass
25-
from google.auth.compute_engine import Credentials as ComputeEngineCredentials
2625

26+
from google.auth.compute_engine import Credentials as ComputeEngineCredentials
27+
from google.auth.transport import requests as google_auth_requests
2728
import requests
2829
import firebase_admin
2930
from firebase_admin import App
@@ -289,10 +290,10 @@ def _update_task_payload(self, task: Task, resource: Resource, extension_id: str
289290
# Meaning that it's credential should be a Compute Engine Credential.
290291
if _Validators.is_non_empty_string(extension_id) and \
291292
isinstance(self._credential, ComputeEngineCredentials):
292-
293+
self._credential.refresh(google_auth_requests.Request())
293294
id_token = self._credential.token
294295
task.http_request['headers'] = \
295-
{**task.http_request['headers'], 'Authorization': f'Bearer ${id_token}'}
296+
{**task.http_request['headers'], 'Authorization': f'Bearer {id_token}'}
296297
# Delete oidc token
297298
del task.http_request['oidc_token']
298299
else:

tests/test_functions.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from datetime import datetime, timedelta, timezone
1818
import json
1919
import time
20+
from unittest import mock
2021
import pytest
2122

2223
import firebase_admin
@@ -152,6 +153,37 @@ def test_task_delete(self):
152153
expected_metrics_header = _utils.get_metrics_header() + ' mock-cred-metric-tag'
153154
assert recorder[0].headers['x-goog-api-client'] == expected_metrics_header
154155

156+
@mock.patch('firebase_admin.functions.isinstance')
157+
def test_task_enqueue_with_extension_refreshes_credential(self, mock_isinstance):
158+
# Force the code to take the ComputeEngineCredentials path
159+
mock_isinstance.return_value = True
160+
161+
# Create a custom response with the extension ID in the resource name
162+
resource_name = (
163+
'projects/test-project/locations/us-central1/queues/'
164+
'ext-test-extension-id-test-function-name/tasks'
165+
)
166+
extension_response = json.dumps({'name': resource_name + '/test-task-id'})
167+
168+
# Instrument the service and get the underlying credential mock
169+
functions_service, recorder = self._instrument_functions_service(payload=extension_response)
170+
mock_credential = functions_service._credential
171+
mock_credential.token = 'mock-id-token'
172+
mock_credential.refresh = mock.MagicMock()
173+
174+
# Create a TaskQueue with an extension ID
175+
queue = functions_service.task_queue('test-function-name', 'test-extension-id')
176+
177+
# Enqueue a task
178+
queue.enqueue(_DEFAULT_DATA)
179+
180+
# Assert that the credential was refreshed
181+
mock_credential.refresh.assert_called_once()
182+
183+
# Assert that the correct token was used in the header
184+
assert len(recorder) == 1
185+
assert recorder[0].headers['Authorization'] == 'Bearer mock-id-token'
186+
155187
class TestTaskQueueOptions:
156188

157189
_DEFAULT_TASK_OPTS = {'schedule_delay_seconds': None, 'schedule_time': None, \

0 commit comments

Comments
 (0)