Skip to content

Commit dbaa9cf

Browse files
committed
fix(identity): add auth_type and resource to AuthBlockingEvent
Derived from the blocking token the same way as the Node SDK, so code written from the blocking functions docs works on Python.
1 parent 4186454 commit dbaa9cf

3 files changed

Lines changed: 113 additions & 1 deletion

File tree

‎src/firebase_functions/identity_fn.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,31 @@ class SmsType(str, Enum):
255255
MULTI_FACTOR_ENROLLMENT = "MULTI_FACTOR_ENROLLMENT"
256256

257257

258+
AuthType = _typing.Literal["USER", "UNAUTHENTICATED"]
259+
"""
260+
Whether the event was triggered by a user or by an unauthenticated request.
261+
"""
262+
263+
264+
@_dataclasses.dataclass(frozen=True)
265+
class AuthBlockingResource:
266+
"""
267+
The resource that emitted an auth blocking event.
268+
"""
269+
270+
service: str
271+
"""
272+
The service that emitted the event.
273+
Example: 'identitytoolkit.googleapis.com'
274+
"""
275+
276+
name: str
277+
"""
278+
The project, or the tenant within it, the event belongs to.
279+
Example: 'projects/my-project' or 'projects/my-project/tenants/my-tenant'
280+
"""
281+
282+
258283
@_dataclasses.dataclass(frozen=True)
259284
class AuthBlockingEvent:
260285
"""
@@ -305,6 +330,12 @@ class AuthBlockingEvent:
305330
sms_type: SmsType | None
306331
"""The type of SMS event."""
307332

333+
auth_type: AuthType
334+
"""Whether the event was triggered by a user or by an unauthenticated request."""
335+
336+
resource: AuthBlockingResource
337+
"""The resource that emitted the event."""
338+
308339
timestamp: _dt.datetime
309340
"""
310341
The time the event was triggered."""

‎src/firebase_functions/private/_identity_fn.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import time as _time
2020
import typing as _typing
2121

22+
import firebase_admin as _firebase_admin
2223
from flask import (
2324
Request as _Request,
2425
)
@@ -215,7 +216,13 @@ def _credential_from_token_data(token_data: dict[str, _typing.Any], time: float)
215216

216217

217218
def _auth_blocking_event_from_token_data(event_type: str, token_data: dict[str, _typing.Any]):
218-
from firebase_functions.identity_fn import AuthBlockingEvent
219+
from firebase_functions.identity_fn import AuthBlockingEvent, AuthBlockingResource
220+
221+
project_id = _firebase_admin.get_app().project_id
222+
tenant_id = token_data.get("tenant_id")
223+
resource_name = (
224+
f"projects/{project_id}/tenants/{tenant_id}" if tenant_id else f"projects/{project_id}"
225+
)
219226

220227
return AuthBlockingEvent(
221228
data=_auth_user_record_from_token_data(token_data["user_record"]),
@@ -229,6 +236,11 @@ def _auth_blocking_event_from_token_data(event_type: str, token_data: dict[str,
229236
credential=_credential_from_token_data(token_data, _time.time()),
230237
email_type=token_data.get("email_type"),
231238
sms_type=token_data.get("sms_type"),
239+
auth_type="USER" if token_data.get("user_record") else "UNAUTHENTICATED",
240+
resource=AuthBlockingResource(
241+
service="identitytoolkit.googleapis.com",
242+
name=resource_name,
243+
),
232244
)
233245

234246

‎tests/test_identity_fn.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,72 @@ def init():
5353
decorated_func(request)
5454

5555
self.assertEqual("world", hello)
56+
57+
def test_handler_receives_auth_type_and_resource(self):
58+
received = None
59+
60+
def func(event):
61+
nonlocal received
62+
received = event
63+
64+
with (
65+
patch.object(_identity_fn, "_token_verifier", token_verifier_mock),
66+
patch.object(_identity_fn._firebase_admin, "get_app", return_value=app_mock),
67+
):
68+
app = Flask(__name__)
69+
with app.test_request_context("/"):
70+
environ = EnvironBuilder(
71+
method="POST",
72+
json={"data": {"jwt": "jwt"}},
73+
).get_environ()
74+
response = identity_fn.before_user_signed_in()(func)(Request(environ))
75+
76+
self.assertEqual(200, response.status_code)
77+
self.assertEqual("USER", received.auth_type)
78+
self.assertEqual(
79+
identity_fn.AuthBlockingResource(
80+
service="identitytoolkit.googleapis.com",
81+
name="projects/project-id",
82+
),
83+
received.resource,
84+
)
85+
86+
87+
app_mock = Mock(project_id="project-id")
88+
89+
90+
class TestAuthBlockingEventFromTokenData(unittest.TestCase):
91+
"""
92+
Tests for building an AuthBlockingEvent from a decoded blocking token.
93+
"""
94+
95+
def _event(self, **token_fields):
96+
token_data = {
97+
**token_verifier_mock.verify_auth_blocking_token.return_value,
98+
**token_fields,
99+
}
100+
with patch.object(_identity_fn._firebase_admin, "get_app", return_value=app_mock):
101+
return _identity_fn._auth_blocking_event_from_token_data(
102+
_identity_fn.event_type_before_sign_in, token_data
103+
)
104+
105+
def test_auth_type_is_user_when_token_has_user_record(self):
106+
self.assertEqual("USER", self._event().auth_type)
107+
108+
def test_resource_names_project_when_token_has_no_tenant(self):
109+
self.assertEqual(
110+
identity_fn.AuthBlockingResource(
111+
service="identitytoolkit.googleapis.com",
112+
name="projects/project-id",
113+
),
114+
self._event().resource,
115+
)
116+
117+
def test_resource_names_tenant_when_token_has_tenant_id(self):
118+
self.assertEqual(
119+
identity_fn.AuthBlockingResource(
120+
service="identitytoolkit.googleapis.com",
121+
name="projects/project-id/tenants/tenant-1",
122+
),
123+
self._event(tenant_id="tenant-1").resource,
124+
)

0 commit comments

Comments
 (0)