Skip to content

Commit 276b96b

Browse files
authored
Set up UserUniqueLogin/IpAddress relationship (#19145)
* Fix ip_address_id type * Update usage of UserUniqueLogin.ip_address * Update tests * Update translations
1 parent 983d1e6 commit 276b96b

File tree

16 files changed

+188
-131
lines changed

16 files changed

+188
-131
lines changed

tests/common/db/accounts.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
UserUniqueLogin,
1818
)
1919

20-
from ...common.constants import REMOTE_ADDR
2120
from .base import WarehouseFactory
2221
from .ip_addresses import IpAddressFactory
2322

@@ -140,11 +139,4 @@ class Meta:
140139
model = UserUniqueLogin
141140

142141
user = factory.SubFactory(UserFactory)
143-
ip_address = REMOTE_ADDR
144-
ip_address_id = factory.LazyAttribute(
145-
lambda o: (
146-
IpAddressFactory.create(ip_address=o.ip_address).id
147-
if o.ip_address is not None
148-
else None
149-
)
150-
)
142+
ip_address = factory.SubFactory(IpAddressFactory)

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ def pyramid_request(pyramid_services, jinja):
210210
dummy_request.user = None
211211
dummy_request.oidc_publisher = None
212212
dummy_request.metrics = dummy_request.find_service(IMetricsService)
213+
dummy_request.ip_address = IpAddressFactory.create()
213214

214215
dummy_request.registry.registerUtility(jinja, IJinja2Environment, name=".jinja2")
215216

tests/functional/manage/test_account_publishing.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from tests.common.constants import REMOTE_ADDR
1111
from tests.common.db.accounts import UserFactory, UserUniqueLoginFactory
12+
from tests.common.db.ip_addresses import IpAddressFactory
1213
from warehouse.accounts.models import UniqueLoginStatus
1314
from warehouse.utils.otp import _get_totp
1415

@@ -27,8 +28,9 @@ def test_add_pending_github_publisher_succeeds(self, webtest):
2728
with_terms_of_service_agreement=True,
2829
clear_pwd="password",
2930
)
31+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
3032
UserUniqueLoginFactory.create(
31-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
33+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
3234
)
3335
# Create a response from GitHub API for owner details
3436
# during form submission validation.
@@ -106,8 +108,9 @@ def test_add_pending_gitlab_publisher_succeeds(self, webtest):
106108
with_terms_of_service_agreement=True,
107109
clear_pwd="password",
108110
)
111+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
109112
UserUniqueLoginFactory.create(
110-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
113+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
111114
)
112115

113116
# Act: Log in

tests/functional/manage/test_organization_publishing.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from tests.common.constants import REMOTE_ADDR
1111
from tests.common.db.accounts import UserFactory, UserUniqueLoginFactory
12+
from tests.common.db.ip_addresses import IpAddressFactory
1213
from tests.common.db.organizations import OrganizationFactory, OrganizationRoleFactory
1314
from warehouse.accounts.models import UniqueLoginStatus
1415
from warehouse.organizations.models import OrganizationRoleType
@@ -29,8 +30,9 @@ def test_add_pending_github_publisher_to_organization(self, webtest):
2930
with_terms_of_service_agreement=True,
3031
clear_pwd="password",
3132
)
33+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
3234
UserUniqueLoginFactory.create(
33-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
35+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
3436
)
3537
organization = OrganizationFactory.create(name="test-organization")
3638
OrganizationRoleFactory.create(
@@ -114,8 +116,9 @@ def test_add_pending_gitlab_publisher_to_organization(self, webtest):
114116
with_terms_of_service_agreement=True,
115117
clear_pwd="password",
116118
)
119+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
117120
UserUniqueLoginFactory.create(
118-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
121+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
119122
)
120123
organization = OrganizationFactory.create(name="test-organization")
121124
OrganizationRoleFactory.create(
@@ -188,8 +191,9 @@ def test_add_pending_google_publisher_to_organization(self, webtest):
188191
with_terms_of_service_agreement=True,
189192
clear_pwd="password",
190193
)
194+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
191195
UserUniqueLoginFactory.create(
192-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
196+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
193197
)
194198
organization = OrganizationFactory.create(name="test-organization")
195199
OrganizationRoleFactory.create(
@@ -260,8 +264,9 @@ def test_add_pending_activestate_publisher_to_organization(self, webtest):
260264
with_terms_of_service_agreement=True,
261265
clear_pwd="password",
262266
)
267+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
263268
UserUniqueLoginFactory.create(
264-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
269+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
265270
)
266271
organization = OrganizationFactory.create(name="test-organization")
267272
OrganizationRoleFactory.create(

tests/functional/manage/test_project_publishing.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from tests.common.constants import REMOTE_ADDR
1111
from tests.common.db.accounts import UserFactory, UserUniqueLoginFactory
12+
from tests.common.db.ip_addresses import IpAddressFactory
1213
from tests.common.db.packaging import ProjectFactory, RoleFactory
1314
from warehouse.accounts.models import UniqueLoginStatus
1415
from warehouse.utils.otp import _get_totp
@@ -30,8 +31,9 @@ def test_add_github_publisher_to_existing_project(self, webtest):
3031
)
3132
project = ProjectFactory.create(name="existing-project")
3233
RoleFactory.create(user=user, project=project, role_name="Owner")
34+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
3335
UserUniqueLoginFactory.create(
34-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
36+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
3537
)
3638

3739
# Mock GitHub API for owner validation
@@ -110,8 +112,9 @@ def test_add_gitlab_publisher_to_existing_project(self, webtest):
110112
)
111113
project = ProjectFactory.create(name="gitlab-project")
112114
RoleFactory.create(user=user, project=project, role_name="Owner")
115+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
113116
UserUniqueLoginFactory.create(
114-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
117+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
115118
)
116119

117120
# Act: Log in

tests/functional/manage/test_views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from warehouse.utils.otp import _get_totp
2121

2222
from ...common.db.accounts import EmailFactory, UserFactory, UserUniqueLoginFactory
23+
from ...common.db.ip_addresses import IpAddressFactory
2324

2425

2526
class TestManageAccount:
@@ -54,8 +55,9 @@ def test_changing_password_succeeds(self, webtest, socket_enabled):
5455
with_terms_of_service_agreement=True,
5556
clear_pwd="password",
5657
)
58+
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
5759
UserUniqueLoginFactory.create(
58-
user=user, ip_address=REMOTE_ADDR, status=UniqueLoginStatus.CONFIRMED
60+
user=user, ip_address=ip_address, status=UniqueLoginStatus.CONFIRMED
5961
)
6062

6163
# visit login page

tests/unit/accounts/test_services.py

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2018,17 +2018,13 @@ def test_factory(self):
20182018

20192019

20202020
class TestDeviceIsKnown:
2021-
def test_device_is_known(self, user_service):
2021+
def test_device_is_known(self, user_service, db_request):
20222022
user = UserFactory.create()
20232023
UserUniqueLoginFactory.create(
2024-
user=user, ip_address=REMOTE_ADDR, status="confirmed"
2024+
user=user, ip_address=db_request.ip_address, status="confirmed"
20252025
)
2026-
request = pretend.stub(
2027-
db=user_service.db,
2028-
remote_addr=REMOTE_ADDR,
2029-
find_service=lambda *a, **kw: pretend.stub(),
2030-
)
2031-
assert user_service.device_is_known(user.id, request)
2026+
db_request.find_service = lambda *a, **kw: pretend.stub()
2027+
assert user_service.device_is_known(user.id, db_request)
20322028

20332029
def test_device_is_not_known(self, user_service, monkeypatch):
20342030
user = UserFactory.create(with_verified_primary_email=True)
@@ -2054,7 +2050,7 @@ def test_device_is_not_known(self, user_service, monkeypatch):
20542050
user_service.db.query(services.UserUniqueLogin)
20552051
.filter(
20562052
services.UserUniqueLogin.user_id == user.id,
2057-
services.UserUniqueLogin.ip_address == REMOTE_ADDR,
2053+
services.UserUniqueLogin.ip_address.has(ip_address=REMOTE_ADDR),
20582054
)
20592055
.one()
20602056
)
@@ -2070,55 +2066,40 @@ def test_device_is_not_known(self, user_service, monkeypatch):
20702066
)
20712067
]
20722068

2073-
def test_device_is_pending_not_expired(self, user_service, monkeypatch):
2069+
def test_device_is_pending_not_expired(self, user_service, monkeypatch, db_request):
20742070
user = UserFactory.create(with_verified_primary_email=True)
20752071
UserUniqueLoginFactory.create(
2076-
user=user, ip_address=REMOTE_ADDR, status="pending"
2072+
user=user, ip_address=db_request.ip_address, status="pending"
20772073
)
20782074
send_email = pretend.call_recorder(lambda *a, **kw: None)
20792075
monkeypatch.setattr(services, "send_unrecognized_login_email", send_email)
20802076
token_service = pretend.stub(dumps=lambda d: "fake_token", max_age=60)
2081-
user_service.request = pretend.stub(
2082-
db=user_service.db,
2083-
remote_addr=REMOTE_ADDR,
2084-
headers={
2085-
"User-Agent": (
2086-
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) "
2087-
"Gecko/20100101 Firefox/15.0.1"
2088-
)
2089-
},
2090-
find_service=lambda *a, **kw: token_service,
2091-
)
2077+
user_service.request = db_request
2078+
db_request.find_service = lambda *a, **kw: token_service
20922079

20932080
assert not user_service.device_is_known(user.id, user_service.request)
20942081
assert send_email.calls == []
20952082

2096-
def test_device_is_pending_and_expired(self, user_service, monkeypatch):
2083+
def test_device_is_pending_and_expired(self, user_service, monkeypatch, db_request):
20972084
user = UserFactory.create(with_verified_primary_email=True)
2098-
ip_address = IpAddressFactory.create(ip_address=REMOTE_ADDR)
20992085
UserUniqueLoginFactory.create(
21002086
user=user,
21012087
status="pending",
2102-
ip_address=str(ip_address.ip_address),
2103-
ip_address_id=ip_address.id,
2088+
ip_address=db_request.ip_address,
21042089
created=datetime.datetime(1970, 1, 1),
21052090
expires=datetime.datetime(1970, 1, 1),
21062091
)
21072092
send_email = pretend.call_recorder(lambda *a, **kw: None)
21082093
monkeypatch.setattr(services, "send_unrecognized_login_email", send_email)
21092094
token_service = pretend.stub(dumps=lambda d: "fake_token", max_age=60)
2110-
user_service.request = pretend.stub(
2111-
db=user_service.db,
2112-
remote_addr=REMOTE_ADDR,
2113-
headers={
2114-
"User-Agent": (
2115-
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) "
2116-
"Gecko/20100101 Firefox/15.0.1"
2117-
)
2118-
},
2119-
find_service=lambda *a, **kw: token_service,
2120-
ip_address=ip_address,
2121-
)
2095+
user_service.request = db_request
2096+
db_request.find_service = lambda *a, **kw: token_service
2097+
db_request.headers = {
2098+
"User-Agent": (
2099+
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) "
2100+
"Gecko/20100101 Firefox/15.0.1"
2101+
)
2102+
}
21222103

21232104
assert not user_service.device_is_known(user.id, user_service.request)
21242105
assert send_email.calls == [

tests/unit/accounts/test_views.py

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -213,53 +213,47 @@ def test_no_query_string_raises_400(self):
213213
with pytest.raises(HTTPBadRequest):
214214
views.accounts_search(pyramid_request)
215215

216-
def test_returns_users_with_prefix(self, db_session, user_service):
216+
def test_returns_users_with_prefix(self, db_request, user_service):
217217
foo = UserFactory.create(username="foo")
218218
bas = [
219219
UserFactory.create(username="bar"),
220220
UserFactory.create(username="baz"),
221221
]
222222

223-
request = pretend.stub(
224-
user=pretend.stub(),
225-
find_service=lambda svc, **kw: {
226-
IUserService: user_service,
227-
IRateLimiter: pretend.stub(
228-
test=pretend.call_recorder(lambda ip_address: True),
229-
hit=pretend.call_recorder(lambda ip_address: None),
230-
),
231-
}[svc],
232-
ip_address=IpAddressFactory.build(),
233-
)
223+
db_request.user = pretend.stub()
224+
db_request.find_service = lambda svc, **kw: {
225+
IUserService: user_service,
226+
IRateLimiter: pretend.stub(
227+
test=pretend.call_recorder(lambda ip_address: True),
228+
hit=pretend.call_recorder(lambda ip_address: None),
229+
),
230+
}[svc]
234231

235-
request.params = MultiDict({"username": "f"})
236-
result = views.accounts_search(request)
232+
db_request.params = MultiDict({"username": "f"})
233+
result = views.accounts_search(db_request)
237234
assert result == {"users": [foo]}
238235

239-
request.params = MultiDict({"username": "ba"})
240-
result = views.accounts_search(request)
236+
db_request.params = MultiDict({"username": "ba"})
237+
result = views.accounts_search(db_request)
241238
assert result == {"users": bas}
242239

243-
request.params = MultiDict({"username": "zzz"})
240+
db_request.params = MultiDict({"username": "zzz"})
244241
with pytest.raises(HTTPNotFound):
245-
views.accounts_search(request)
242+
views.accounts_search(db_request)
246243

247-
def test_when_rate_limited(self, db_session):
244+
def test_when_rate_limited(self, db_request):
248245
search_limiter = pretend.stub(
249246
test=pretend.call_recorder(lambda ip_address: False),
250247
)
251-
request = pretend.stub(
252-
user=pretend.stub(),
253-
find_service=lambda svc, **kw: {
254-
IRateLimiter: search_limiter,
255-
}[svc],
256-
ip_address=IpAddressFactory.build(),
257-
)
248+
db_request.user = pretend.stub()
249+
db_request.find_service = lambda svc, **kw: {
250+
IRateLimiter: search_limiter,
251+
}[svc]
258252

259-
request.params = MultiDict({"username": "foo"})
260-
result = views.accounts_search(request)
253+
db_request.params = MultiDict({"username": "foo"})
254+
result = views.accounts_search(db_request)
261255

262-
assert search_limiter.test.calls == [pretend.call(request.ip_address)]
256+
assert search_limiter.test.calls == [pretend.call(db_request.ip_address)]
263257
assert result == {"users": []}
264258

265259

@@ -641,8 +635,7 @@ def test_login_with_remembered_device_confirms_unique_login(
641635

642636
UserUniqueLoginFactory.create(
643637
user=user,
644-
ip_address=str(db_request.ip_address.ip_address),
645-
ip_address_id=db_request.ip_address.id,
638+
ip_address=db_request.ip_address,
646639
status=UniqueLoginStatus.PENDING,
647640
)
648641

@@ -700,8 +693,7 @@ def test_login_updates_last_used(self, monkeypatch, db_request, pyramid_services
700693
past_timestamp = datetime.datetime(1970, 1, 1)
701694
UserUniqueLoginFactory.create(
702695
user=user,
703-
ip_address=str(db_request.ip_address.ip_address),
704-
ip_address_id=db_request.ip_address.id,
696+
ip_address=db_request.ip_address,
705697
status=UniqueLoginStatus.CONFIRMED,
706698
last_used=past_timestamp,
707699
)
@@ -5475,7 +5467,8 @@ def test_unique_login_not_found(self, db_request):
54755467

54765468
def test_ip_address_mismatch(self, db_request):
54775469
user = UserFactory.create(last_login=datetime.datetime.now(datetime.UTC))
5478-
unique_login = UserUniqueLoginFactory.create(user=user, ip_address="1.1.1.1")
5470+
ip_address = IpAddressFactory.create(ip_address="1.1.1.1")
5471+
unique_login = UserUniqueLoginFactory.create(user=user, ip_address=ip_address)
54795472
db_request.user = None
54805473
db_request.params = {"token": "foo"}
54815474
token_data = {
@@ -5510,8 +5503,7 @@ def test_success(self, monkeypatch, db_request):
55105503
user = UserFactory.create(last_login=datetime.datetime.now(datetime.UTC))
55115504
unique_login = UserUniqueLoginFactory.create(
55125505
user=user,
5513-
ip_address=str(db_request.ip_address.ip_address),
5514-
ip_address_id=db_request.ip_address.id,
5506+
ip_address=db_request.ip_address,
55155507
)
55165508
db_request.user = None
55175509
db_request.params = {"token": "foo"}

0 commit comments

Comments
 (0)