File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -170,6 +170,20 @@ async def test_no_email_attribute_falls_back_to_user_id(self):
170170 # _user() with no email omits the attribute; getattr returns None.
171171 assert await service .is_enabled ("test_flag" , _user (USER_A )) is True
172172
173+ @pytest .mark .asyncio
174+ async def test_email_only_allowlist_denies_stub_without_email_attribute (self ):
175+ # Pins _email_of's getattr fallback: a user object with NO email
176+ # attribute at all (not CurrentUser with email=None) against an
177+ # email-ONLY allowlist → graceful deny, no AttributeError.
178+ flag = _flag (
179+ rollout_type = RolloutType .ALLOWLIST ,
180+ allowlist_emails = ["alice@example.com" ],
181+ )
182+ service , _ , _ = make_service (flag = flag )
183+ stub = _user (USER_A ) # SimpleNamespace; email attribute omitted
184+ assert not hasattr (stub , "email" )
185+ assert await service .is_enabled ("test_flag" , stub ) is False
186+
173187 @pytest .mark .asyncio
174188 async def test_current_user_email_field_flows_through (self ):
175189 """CurrentUser.email (JWT "email" claim / UserDoc email) satisfies
Original file line number Diff line number Diff line change @@ -235,6 +235,21 @@ async def test_jwt_blank_email_claim_yields_none_email(self):
235235 assert result is not None
236236 assert result .email is None
237237
238+ @pytest .mark .asyncio
239+ @pytest .mark .parametrize ("bad_email" , [42 , ["alice@example.com" ]])
240+ async def test_jwt_non_string_email_claim_yields_none_email (self , bad_email ):
241+ # A token whose "email" claim is not a string (int, list, …) must
242+ # still authenticate — the claim parses to None, never an error.
243+ token = make_jwt_token (email = bad_email )
244+ req = make_request (auth_header = f"Bearer { token } " )
245+
246+ with patch ("dependencies.auth.get_settings" , return_value = make_settings ()):
247+ result = await get_current_user (req , db = MagicMock ())
248+
249+ assert result is not None
250+ assert result .user_id == USER_OID
251+ assert result .email is None
252+
238253 @pytest .mark .asyncio
239254 async def test_token_factory_round_trip_populates_email (self ):
240255 # Mint with the real TokenFactory → resolve via get_current_user:
You can’t perform that action at this time.
0 commit comments