Skip to content

Commit ab6579d

Browse files
committed
Move typo checks to after_insert
1 parent 0522fe7 commit ab6579d

File tree

2 files changed

+92
-80
lines changed

2 files changed

+92
-80
lines changed

warehouse/packaging/models.py

+89
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
Text,
3535
UniqueConstraint,
3636
cast,
37+
event,
3738
func,
3839
or_,
3940
orm,
@@ -67,6 +68,7 @@
6768
from warehouse.classifiers.models import Classifier
6869
from warehouse.events.models import HasEvents
6970
from warehouse.forklift import metadata
71+
from warehouse.helpdesk.interfaces import IAdminNotificationService
7072
from warehouse.integrations.vulnerabilities.models import VulnerabilityRecord
7173
from warehouse.observations.models import HasObservations
7274
from warehouse.organizations.models import (
@@ -77,6 +79,10 @@
7779
Team,
7880
TeamProjectRole,
7981
)
82+
from warehouse.packaging.interfaces import (
83+
IProjectService,
84+
ProjectNameUnavailableTypoSquattingError,
85+
)
8086
from warehouse.sitemap.models import SitemapMixin
8187
from warehouse.utils import dotted_navigator, wheel
8288
from warehouse.utils.attrs import make_repr
@@ -496,6 +502,89 @@ def yanked_releases(self):
496502
)
497503

498504

505+
@event.listens_for(Project, "after_insert")
506+
def receive_after_insert(mapper, connection, project):
507+
request = get_current_request()
508+
project_service = request.find_service(IProjectService)
509+
name = project.name
510+
try:
511+
project_service.check_project_name_after_insert(name)
512+
except ProjectNameUnavailableTypoSquattingError as exc:
513+
request.log.warning(
514+
"ProjectNameUnavailableTypoSquattingError",
515+
check_name=exc.check_name,
516+
existing_project_name=exc.existing_project_name,
517+
)
518+
# Send notification to Admins for review
519+
notification_service = request.find_service(IAdminNotificationService)
520+
521+
warehouse_domain = request.registry.settings.get("warehouse.domain")
522+
new_project_page = request.route_url(
523+
"packaging.project",
524+
name=name,
525+
_host=warehouse_domain,
526+
)
527+
new_project_text = (
528+
f"During `file_upload`, Project Create for "
529+
f"*<{new_project_page}|{name}>* was detected as a potential "
530+
f"typo by the `{exc.check_name!r}` check."
531+
)
532+
existing_project_page = request.route_url(
533+
"packaging.project",
534+
name=exc.existing_project_name,
535+
_host=warehouse_domain,
536+
)
537+
existing_project_text = (
538+
f"<{existing_project_page}|Existing project: "
539+
f"{exc.existing_project_name}>"
540+
)
541+
542+
webhook_payload = {
543+
"blocks": [
544+
{
545+
"type": "header",
546+
"text": {
547+
"type": "plain_text",
548+
"text": "TypoSnyper :warning:",
549+
"emoji": True,
550+
},
551+
},
552+
{
553+
"type": "section",
554+
"text": {
555+
"type": "mrkdwn",
556+
"text": new_project_text,
557+
},
558+
},
559+
{
560+
"type": "section",
561+
"text": {
562+
"type": "mrkdwn",
563+
"text": existing_project_text,
564+
},
565+
},
566+
{"type": "divider"},
567+
{
568+
"type": "context",
569+
"elements": [
570+
{
571+
"type": "plain_text",
572+
"text": "Once reviewed/confirmed, "
573+
"react to this message with :white_check_mark:",
574+
"emoji": True,
575+
}
576+
],
577+
},
578+
]
579+
}
580+
notification_service.send_notification(payload=webhook_payload)
581+
582+
request.metrics.increment(
583+
"warehouse.packaging.services.create_project.typo_squatting",
584+
tags=[f"check_name:{exc.check_name!r}"],
585+
)
586+
587+
499588
class DependencyKind(enum.IntEnum):
500589
requires = 1
501590
provides = 2

warehouse/packaging/services.py

+3-80
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
from warehouse.admin.flags import AdminFlagValue
3737
from warehouse.email import send_pending_trusted_publisher_invalidated_email
3838
from warehouse.events.tags import EventTag
39-
from warehouse.helpdesk.interfaces import IAdminNotificationService
4039
from warehouse.metrics import IMetricsService
4140
from warehouse.oidc.models import PendingOIDCPublisher
4241
from warehouse.packaging.interfaces import (
@@ -485,6 +484,9 @@ def check_project_name(self, name: str) -> None:
485484
).first():
486485
raise ProjectNameUnavailableSimilarError(similar_project_name)
487486

487+
return None
488+
489+
def check_project_name_after_insert(self, name: str) -> None:
488490
# Check for typo-squatting.
489491
if typo_check_match := typo_check_name(canonicalize_name(name)):
490492
raise ProjectNameUnavailableTypoSquattingError(
@@ -558,85 +560,6 @@ def create_project(
558560
projecthelp=request.help_url(_anchor="project-name"),
559561
),
560562
) from None
561-
except ProjectNameUnavailableTypoSquattingError as exc:
562-
# Don't yet raise an error here, as we want to allow the
563-
# user to proceed with the project creation. We'll log a warning
564-
# instead.
565-
request.log.warning(
566-
"ProjectNameUnavailableTypoSquattingError",
567-
check_name=exc.check_name,
568-
existing_project_name=exc.existing_project_name,
569-
)
570-
# Send notification to Admins for review
571-
notification_service = request.find_service(IAdminNotificationService)
572-
573-
warehouse_domain = request.registry.settings.get("warehouse.domain")
574-
new_project_page = request.route_url(
575-
"packaging.project",
576-
name=name,
577-
_host=warehouse_domain,
578-
)
579-
new_project_text = (
580-
f"During `file_upload`, Project Create for "
581-
f"*<{new_project_page}|{name}>* was detected as a potential "
582-
f"typo by the `{exc.check_name!r}` check."
583-
)
584-
existing_project_page = request.route_url(
585-
"packaging.project",
586-
name=exc.existing_project_name,
587-
_host=warehouse_domain,
588-
)
589-
existing_project_text = (
590-
f"<{existing_project_page}|Existing project: "
591-
f"{exc.existing_project_name}>"
592-
)
593-
594-
webhook_payload = {
595-
"blocks": [
596-
{
597-
"type": "header",
598-
"text": {
599-
"type": "plain_text",
600-
"text": "TypoSnyper :warning:",
601-
"emoji": True,
602-
},
603-
},
604-
{
605-
"type": "section",
606-
"text": {
607-
"type": "mrkdwn",
608-
"text": new_project_text,
609-
},
610-
},
611-
{
612-
"type": "section",
613-
"text": {
614-
"type": "mrkdwn",
615-
"text": existing_project_text,
616-
},
617-
},
618-
{"type": "divider"},
619-
{
620-
"type": "context",
621-
"elements": [
622-
{
623-
"type": "plain_text",
624-
"text": "Once reviewed/confirmed, "
625-
"react to this message with :white_check_mark:",
626-
"emoji": True,
627-
}
628-
],
629-
},
630-
]
631-
}
632-
notification_service.send_notification(payload=webhook_payload)
633-
634-
request.metrics.increment(
635-
"warehouse.packaging.services.create_project.typo_squatting",
636-
tags=[f"check_name:{exc.check_name!r}"],
637-
)
638-
# and continue with the project creation
639-
pass
640563

641564
# The project name is valid: create it and add it
642565
project = Project(name=name)

0 commit comments

Comments
 (0)