Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tests/unit/admin/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
24 changes: 17 additions & 7 deletions tests/unit/admin/views/test_ipaddresses.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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]}
2 changes: 1 addition & 1 deletion warehouse/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)

Expand Down
44 changes: 41 additions & 3 deletions warehouse/admin/templates/admin/ip_addresses/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

{% import "admin/utils/pagination.html" as pagination %}

{% block title %}{{ ip_address.id }}{% endblock %}
{% block title %}{{ ip_address }}{% endblock %}

{% block breadcrumb %}
<li class="breadcrumb-item"><a href="{{ request.route_path('admin.ip_address.list') }}">IP Addresses</a></li>
<li class="breadcrumb-item active">{{ ip_address.id }}</li>
<li class="breadcrumb-item active">{{ ip_address }}</li>
{% endblock %}

{% block content %}
Expand All @@ -22,6 +22,9 @@ <h3 class="card-title"><code>IpAddress</code> Record</h3>
<dt class="col-sm-4">IP Address:</dt>
<dd class="col-sm-8">{{ ip_address.ip_address }}</dd>

<dt class="col-sm-4">IP Address ID:</dt>
<dd class="col-sm-8">{{ ip_address.ip_address.id }}</dd>

<dt class="col-sm-4">Hashed IP Address:</dt>
<dd class="col-sm-8">{{ ip_address.hashed_ip_address }}</dd>

Expand All @@ -38,5 +41,40 @@ <h3 class="card-title"><code>IpAddress</code> Record</h3>
<dd class="col-sm-8">{{ ip_address.ban_reason.value }}</dd>
</dl>
</div> <!-- /.card-body -->
</div>
</div> <!-- /.card -->

<div class="card">
<div class="card-header with-border">
<h3 class="card-title">Unique logins</h3>
</div>

<div class="card-body">
{% if unique_logins %}
<div class="table-responsive p-0">
<table class="table table-hover" id="pending-oidc-publishers">
<thead>
<tr>
<th scope="col">Created</th>
<th scope="col">User</th>
<th scope="col">Status</th>
<th scope="col">Device Information</th>
</tr>
</thead>
<tbody>
{% for login in unique_logins %}
<tr>
<td>{{ login.created }}</td>
<td><a href="{{ request.route_path('admin.user.detail', username=login.user.username ) }}">{{ login.user.username }}</a></td>
<td>{{ login.status.value }}</td>
<td>{{ login.device_information }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
No known logins.
{% endif %}
</div> <!-- /.card-body -->
</div> <!-- /.card -->
{% endblock %}
2 changes: 1 addition & 1 deletion warehouse/admin/templates/admin/users/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,7 @@ <h3 class="card-title">Unique logins</h3>
{% for login in user.unique_logins %}
<tr>
<td>{{ login.created }}</td>
<td>{{ login.ip_address }}</td>
<td><a href="{{ request.route_path('admin.ip_address.detail', ip_address=login.ip_address ) }}">{{ login.ip_address }}</a></td>
<td>{{ login.status.value }}</td>
<td>{{ login.device_information }}</td>
</tr>
Expand Down
15 changes: 11 additions & 4 deletions warehouse/admin/views/ip_addresses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}