diff --git a/tests/unit/admin/test_routes.py b/tests/unit/admin/test_routes.py index 78e9ee91d73f..061278902b7a 100644 --- a/tests/unit/admin/test_routes.py +++ b/tests/unit/admin/test_routes.py @@ -225,7 +225,7 @@ def test_includeme(): pretend.call("admin.ip_address.list", "/admin/ip-addresses/", domain=warehouse), pretend.call( "admin.ip_address.detail", - "/admin/ip-addresses/{ip_address_id}", + "/admin/ip-addresses/{ip_address}", domain=warehouse, ), pretend.call("admin.project.list", "/admin/projects/", domain=warehouse), diff --git a/tests/unit/admin/views/test_ipaddresses.py b/tests/unit/admin/views/test_ipaddresses.py index df3fde80208c..cc6f62203d83 100644 --- a/tests/unit/admin/views/test_ipaddresses.py +++ b/tests/unit/admin/views/test_ipaddresses.py @@ -1,12 +1,11 @@ # SPDX-License-Identifier: Apache-2.0 -import uuid - import pretend import pytest from pyramid.httpexceptions import HTTPBadRequest +from tests.common.db.accounts import UserUniqueLoginFactory from tests.common.db.ip_addresses import IpAddressFactory from warehouse.admin.views import ip_addresses as ip_views @@ -45,21 +44,32 @@ def test_with_invalid_page(self): class TestIpAddressDetail: def test_no_ip_address(self, db_request): - db_request.matchdict["ip_address_id"] = None + db_request.matchdict["ip_address"] = None with pytest.raises(HTTPBadRequest): ip_views.ip_address_detail(db_request) def test_ip_address_not_found(self, db_request): - db_request.matchdict["ip_address_id"] = uuid.uuid4() + db_request.matchdict["ip_address"] = "69.69.69.69" with pytest.raises(HTTPBadRequest): ip_views.ip_address_detail(db_request) - def test_ip_address_found(self, db_request): + def test_ip_address_found_no_unique_logins(self, db_request): ip_address = IpAddressFactory() - db_request.matchdict["ip_address_id"] = ip_address.id + db_request.matchdict["ip_address"] = str(ip_address.ip_address) + + result = ip_views.ip_address_detail(db_request) + + assert result == {"ip_address": ip_address, "unique_logins": []} + + def test_ip_address_found_with_unique_logins(self, db_request): + ip_address = IpAddressFactory() + unique_login = UserUniqueLoginFactory.create( + ip_address=str(ip_address.ip_address) + ) + db_request.matchdict["ip_address"] = str(ip_address.ip_address) result = ip_views.ip_address_detail(db_request) - assert result == {"ip_address": ip_address} + assert result == {"ip_address": ip_address, "unique_logins": [unique_login]} diff --git a/warehouse/admin/routes.py b/warehouse/admin/routes.py index eb7f7e230eac..8012ca66ddf2 100644 --- a/warehouse/admin/routes.py +++ b/warehouse/admin/routes.py @@ -227,7 +227,7 @@ def includeme(config): config.add_route("admin.ip_address.list", "/admin/ip-addresses/", domain=warehouse) config.add_route( "admin.ip_address.detail", - "/admin/ip-addresses/{ip_address_id}", + "/admin/ip-addresses/{ip_address}", domain=warehouse, ) diff --git a/warehouse/admin/templates/admin/ip_addresses/detail.html b/warehouse/admin/templates/admin/ip_addresses/detail.html index db0dc8c4a166..6350122027ef 100644 --- a/warehouse/admin/templates/admin/ip_addresses/detail.html +++ b/warehouse/admin/templates/admin/ip_addresses/detail.html @@ -4,11 +4,11 @@ {% import "admin/utils/pagination.html" as pagination %} -{% block title %}{{ ip_address.id }}{% endblock %} +{% block title %}{{ ip_address }}{% endblock %} {% block breadcrumb %} - + {% endblock %} {% block content %} @@ -22,6 +22,9 @@

IpAddress Record

IP Address:
{{ ip_address.ip_address }}
+
IP Address ID:
+
{{ ip_address.ip_address.id }}
+
Hashed IP Address:
{{ ip_address.hashed_ip_address }}
@@ -38,5 +41,40 @@

IpAddress Record

{{ ip_address.ban_reason.value }}
- + + +
+
+

Unique logins

+
+ +
+ {% if unique_logins %} +
+ + + + + + + + + + + {% for login in unique_logins %} + + + + + + + {% endfor %} + +
CreatedUserStatusDevice Information
{{ login.created }}{{ login.user.username }}{{ login.status.value }}{{ login.device_information }}
+
+ {% else %} + No known logins. + {% endif %} +
+
{% endblock %} diff --git a/warehouse/admin/templates/admin/users/detail.html b/warehouse/admin/templates/admin/users/detail.html index 08afbf23cfc3..2c05a372fc48 100644 --- a/warehouse/admin/templates/admin/users/detail.html +++ b/warehouse/admin/templates/admin/users/detail.html @@ -1009,7 +1009,7 @@

Unique logins

{% for login in user.unique_logins %} {{ login.created }} - {{ login.ip_address }} + {{ login.ip_address }} {{ login.status.value }} {{ login.device_information }} diff --git a/warehouse/admin/views/ip_addresses.py b/warehouse/admin/views/ip_addresses.py index 75892249e751..aaa543b186f5 100644 --- a/warehouse/admin/views/ip_addresses.py +++ b/warehouse/admin/views/ip_addresses.py @@ -9,6 +9,7 @@ from pyramid.view import view_config from sqlalchemy.exc import NoResultFound +from warehouse.accounts.models import UserUniqueLogin from warehouse.authnz import Permissions from warehouse.ip_addresses.models import IpAddress from warehouse.utils.paginate import paginate_url_factory @@ -51,10 +52,16 @@ def ip_address_list(request: Request) -> dict[str, SQLAlchemyORMPage[IpAddress] uses_session=True, ) def ip_address_detail(request: Request) -> dict[str, IpAddress]: - ip_address_id = request.matchdict["ip_address_id"] + ip_address = request.matchdict["ip_address"] try: - ip_address = request.db.query(IpAddress).filter_by(id=ip_address_id).one() + ip_address = request.db.query(IpAddress).filter_by(ip_address=ip_address).one() except NoResultFound: - raise HTTPBadRequest("No IP Address found with that id.") + raise HTTPBadRequest("No matching IP Address found.") - return {"ip_address": ip_address} + unique_logins = ( + request.db.query(UserUniqueLogin) + .filter(UserUniqueLogin.ip_address == str(ip_address.ip_address)) + .all() + ) + + return {"ip_address": ip_address, "unique_logins": unique_logins}