Skip to content

Commit

Permalink
feat(admin): freeze on quarantine, add user observation on removal (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
miketheman authored Nov 19, 2024
1 parent 3e117d5 commit 1c00f2d
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 18 deletions.
19 changes: 14 additions & 5 deletions tests/unit/admin/views/test_malware_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ def test_malware_reports_project_verdict_not_malware(self, db_request):
assert action_record["reason"] == "This is a test"

def test_malware_reports_project_verdict_quarantine(self, db_request):
owner_user = UserFactory.create(is_frozen=False)
project = ProjectFactory.create()
RoleFactory(user=owner_user, project=project, role_name="Owner")
report = ProjectObservationFactory.create(kind="is_malware", related=project)

db_request.route_path = lambda a: "/admin/malware_reports/"
Expand Down Expand Up @@ -130,6 +132,7 @@ def test_malware_reports_project_verdict_quarantine(self, db_request):
== f"Quarantined by {db_request.user.username}."
)
assert len(report.actions) == 0
assert owner_user.is_frozen is True

def test_malware_reports_project_verdict_remove_malware(self, db_request):
owner_user = UserFactory.create(is_frozen=False)
Expand All @@ -148,7 +151,7 @@ def test_malware_reports_project_verdict_remove_malware(self, db_request):
db_request.session = pretend.stub(
flash=pretend.call_recorder(lambda *a, **kw: None)
)
db_request.user = owner_user
db_request.user = UserFactory.create()

result = views.malware_reports_project_verdict_remove_malware(
project, db_request
Expand All @@ -167,7 +170,8 @@ def test_malware_reports_project_verdict_remove_malware(self, db_request):

assert len(report.actions) == 1
assert db_request.db.get(Project, project.id) is None
assert db_request.user.is_frozen is True
assert owner_user.is_frozen is True
assert owner_user.observations[0].kind == "account_abuse"


class TestMalwareReportsDetail:
Expand Down Expand Up @@ -211,7 +215,10 @@ def test_detail_not_malware_for_project(self, db_request):
assert action_record["reason"] == "This is a test"

def test_detail_verdict_quarantine_project(self, db_request):
report = ProjectObservationFactory.create(kind="is_malware")
owner_user = UserFactory.create(is_frozen=False)
project = ProjectFactory.create()
RoleFactory(user=owner_user, project=project, role_name="Owner")
report = ProjectObservationFactory.create(kind="is_malware", related=project)
db_request.matchdict["observation_id"] = str(report.id)
db_request.route_path = lambda a: "/admin/malware_reports/"
db_request.session = pretend.stub(
Expand All @@ -237,6 +244,7 @@ def test_detail_verdict_quarantine_project(self, db_request):
f"Quarantined by {db_request.user.username}."
)
assert len(report.actions) == 0
assert owner_user.is_frozen is True

def test_detail_remove_malware_for_project(self, db_request):
owner_user = UserFactory.create(is_frozen=False)
Expand All @@ -257,7 +265,7 @@ def test_detail_remove_malware_for_project(self, db_request):
db_request.session = pretend.stub(
flash=pretend.call_recorder(lambda *a, **kw: None)
)
db_request.user = owner_user
db_request.user = UserFactory.create()

result = views.remove_malware_for_project(db_request)

Expand All @@ -273,4 +281,5 @@ def test_detail_remove_malware_for_project(self, db_request):

assert len(report.actions) == 1
assert db_request.db.get(Project, project.id) is None
assert db_request.user.is_frozen is True
assert owner_user.is_frozen is True
assert owner_user.observations[0].kind == "account_abuse"
27 changes: 16 additions & 11 deletions warehouse/admin/static/js/warehouse.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,22 @@ if (tokenTable.length) {
}

// Observations
let observationsTable = $("#observations");
if (observationsTable.length) {
let table = observationsTable.DataTable({
responsive: true,
lengthChange: false,
});
table.column(".time").order("desc").draw();
table.columns([".payload"]).visible(false);
new $.fn.dataTable.Buttons(table, {buttons: ["copy", "csv", "colvis"]});
table.buttons().container().appendTo($(".col-md-6:eq(0)", table.table().container()));
}
// Note: Each of these tables **must** have the same columns for this to work.
const tableSelectors = ["#observations", "#user_observations"];

tableSelectors.forEach(selector => {
let tableElement = $(selector);
if (tableElement.length) {
let table = tableElement.DataTable({
responsive: true,
lengthChange: false,
});
table.column(".time").order("desc").draw();
table.columns([".payload"]).visible(false);
new $.fn.dataTable.Buttons(table, {buttons: ["copy", "csv", "colvis"]});
table.buttons().container().appendTo($(".col-md-6:eq(0)", table.table().container()));
}
});

// Malware Reports
let malwareReportsTable = $("#malware-reports");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ <h4 class="modal-title">Quarantine Project</h4>
</p>
<p>
This will remove the Project from being installable,
freeze the Owner's account,
and prohibit the Project from being changed by the Owner.
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ <h4 class="modal-title">Quarantine Project</h4>
</p>
<p>
This will remove the Project from being installable,
freeze the Owner's account,
and prohibit the Project from being changed by the Owner.
</p>
</div>
Expand Down
34 changes: 33 additions & 1 deletion warehouse/admin/templates/admin/users/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -883,10 +883,42 @@ <h3 class="card-title">Account activity</h3>
</div>
</div> <!-- .card -->

{% if user.observations %}
<div class="card card-warning card-outline">
<div class="card-header with-border">
<h3 class="card-title">Observations about <code>{{ user.username }}</code></h3>
</div>
<div class="card-body">
<table id="user_observations" class="table table-striped table-hover">
<thead>
<tr>
<th scope="col" class="time">Time</th>
<th scope="col" class="related">Related</th>
<th scope="col" class="kind">Kind</th>
<th scope="col" class="summary">Summary</th>
<th scope="col" class="payload">Payload</th>
</tr>
</thead>
<tbody>
{% for observation in user.observations %}
<tr>
<td>{{ observation.created }}</td>
<td>{{ observation.related }}</td>
<td>{{ observation.kind_display }}</td>
<td>{{ observation.summary }}</td>
<td>{{ observation.payload }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div> <!-- .card -->
{% endif %}

{% if user.observer.observations %}
<div class="card">
<div class="card-header with-border">
<h3 class="card-title">Observations</h3>
<h3 class="card-title">Admin-submitted Observations</h3>
</div>
<div class="card-body">
<table id="observations" class="table table-striped table-hover">
Expand Down
16 changes: 15 additions & 1 deletion warehouse/admin/views/malware_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from warehouse.authnz import Permissions
from warehouse.helpdesk.interfaces import IHelpDeskService
from warehouse.observations.models import Observation
from warehouse.observations.models import Observation, ObservationKind
from warehouse.utils.project import (
confirm_project,
prohibit_and_remove_project,
Expand Down Expand Up @@ -161,6 +161,13 @@ def malware_reports_project_verdict_remove_malware(project, request):
# freeze associated user accounts
for user in project.users:
user.is_frozen = True
user.record_observation(
request=request,
kind=ObservationKind.AccountAbuse,
actor=request.user,
summary="Account frozen due to malware report",
payload={"project_name": project.name},
)

# add action to all Malware Observations, **before we remove the project**
observations = (
Expand Down Expand Up @@ -289,6 +296,13 @@ def remove_malware_for_project(request):
# freeze associated user accounts
for user in project.users:
user.is_frozen = True
user.record_observation(
request=request,
kind=ObservationKind.AccountAbuse,
actor=request.user,
summary="Account frozen due to malware report",
payload={"project_name": project.name},
)

now = datetime.now(tz=timezone.utc)

Expand Down
1 change: 1 addition & 0 deletions warehouse/observations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class ObservationKind(enum.Enum):
IsMalware = ("is_malware", "Is Malware")
IsSpam = ("is_spam", "Is Spam")
SomethingElse = ("something_else", "Something Else")
AccountAbuse = ("account_abuse", "Account Abuse")
AccountRecovery = (
"account_recovery",
"Account Recovery",
Expand Down
4 changes: 4 additions & 0 deletions warehouse/utils/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def quarantine_project(project: Project, request, flash=True) -> None:
)
)

# freeze associated user accounts
for user in project.users:
user.is_frozen = True

if flash:
request.session.flash(
f"Project {project.name} quarantined.\n"
Expand Down

0 comments on commit 1c00f2d

Please sign in to comment.