Skip to content

Commit 97ed8aa

Browse files
committed
WIP
1 parent d3a8710 commit 97ed8aa

File tree

4 files changed

+101
-94
lines changed

4 files changed

+101
-94
lines changed

tests/unit/packaging/test_services.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,7 @@ def test_check_project_name_typosquatting_prohibited(self, db_session):
10571057
ProhibitedProjectFactory.create(name="numpy")
10581058

10591059
with pytest.raises(ProjectNameUnavailableTypoSquattingError):
1060-
service.check_project_name_after_insert("numpi")
1060+
service.check_project_name_after_commit("numpi")
10611061

10621062
def test_check_project_name_ok(self, db_session):
10631063
service = ProjectService(session=db_session)

warehouse/packaging/models.py

-92
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
Text,
3535
UniqueConstraint,
3636
cast,
37-
event,
3837
func,
3938
or_,
4039
orm,
@@ -68,7 +67,6 @@
6867
from warehouse.classifiers.models import Classifier
6968
from warehouse.events.models import HasEvents
7069
from warehouse.forklift import metadata
71-
from warehouse.helpdesk.interfaces import IAdminNotificationService
7270
from warehouse.integrations.vulnerabilities.models import VulnerabilityRecord
7371
from warehouse.observations.models import HasObservations
7472
from warehouse.organizations.models import (
@@ -79,10 +77,6 @@
7977
Team,
8078
TeamProjectRole,
8179
)
82-
from warehouse.packaging.interfaces import (
83-
IProjectService,
84-
ProjectNameUnavailableTypoSquattingError,
85-
)
8680
from warehouse.sitemap.models import SitemapMixin
8781
from warehouse.utils import dotted_navigator, wheel
8882
from warehouse.utils.attrs import make_repr
@@ -502,92 +496,6 @@ def yanked_releases(self):
502496
)
503497

504498

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

warehouse/packaging/services.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ def check_project_name(self, name: str) -> None:
486486

487487
return None
488488

489-
def check_project_name_after_insert(self, name: str) -> None:
489+
def check_project_name_after_commit(self, name: str) -> None:
490490
# Check for typo-squatting.
491491
if typo_check_match := typo_check_name(canonicalize_name(name)):
492492
raise ProjectNameUnavailableTypoSquattingError(

warehouse/packaging/typosnyper.py

+99
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@
2525

2626
from itertools import permutations
2727

28+
from pyramid.threadlocal import get_current_request
29+
30+
from warehouse import db
31+
from warehouse.helpdesk.interfaces import IAdminNotificationService
32+
from warehouse.packaging.interfaces import (
33+
IProjectService,
34+
ProjectNameUnavailableTypoSquattingError,
35+
)
36+
from warehouse.packaging.models import Project
37+
2838
# Ensure all checks return a similar type,
2939
# where the first string is the check name,
3040
# followed by the matched project name,
@@ -431,3 +441,92 @@ def typo_check_name(project_name: str, corpus=None) -> TypoCheckMatch:
431441
if result := check(project_name, corpus=corpus):
432442
return result
433443
return None
444+
445+
446+
@db.listens_for(db.Session, "after_commit")
447+
def check_typo_after_commit(config, session):
448+
# Go through each new object and find any Project instances
449+
for obj in session.new:
450+
if obj.__class__ == Project:
451+
request = get_current_request()
452+
if not request:
453+
# Can't do anything if there isn't a request
454+
return
455+
project_service = request.find_service(IProjectService)
456+
name = obj.name
457+
try:
458+
project_service.check_project_name_after_commit(name)
459+
except ProjectNameUnavailableTypoSquattingError as exc:
460+
request.log.warning(
461+
"ProjectNameUnavailableTypoSquattingError",
462+
check_name=exc.check_name,
463+
existing_project_name=exc.existing_project_name,
464+
)
465+
# Send notification to Admins for review
466+
notification_service = request.find_service(IAdminNotificationService)
467+
468+
warehouse_domain = request.registry.settings.get("warehouse.domain")
469+
new_project_page = request.route_url(
470+
"packaging.project",
471+
name=name,
472+
_host=warehouse_domain,
473+
)
474+
new_project_text = (
475+
f"During `file_upload`, Project Create for "
476+
f"*<{new_project_page}|{name}>* was detected as a potential "
477+
f"typo by the `{exc.check_name!r}` check."
478+
)
479+
existing_project_page = request.route_url(
480+
"packaging.project",
481+
name=exc.existing_project_name,
482+
_host=warehouse_domain,
483+
)
484+
existing_project_text = (
485+
f"<{existing_project_page}|Existing project: "
486+
f"{exc.existing_project_name}>"
487+
)
488+
489+
webhook_payload = {
490+
"blocks": [
491+
{
492+
"type": "header",
493+
"text": {
494+
"type": "plain_text",
495+
"text": "TypoSnyper :warning:",
496+
"emoji": True,
497+
},
498+
},
499+
{
500+
"type": "section",
501+
"text": {
502+
"type": "mrkdwn",
503+
"text": new_project_text,
504+
},
505+
},
506+
{
507+
"type": "section",
508+
"text": {
509+
"type": "mrkdwn",
510+
"text": existing_project_text,
511+
},
512+
},
513+
{"type": "divider"},
514+
{
515+
"type": "context",
516+
"elements": [
517+
{
518+
"type": "plain_text",
519+
"text": "Once reviewed/confirmed, "
520+
"react to this message with :white_check_mark:",
521+
"emoji": True,
522+
}
523+
],
524+
},
525+
]
526+
}
527+
notification_service.send_notification(payload=webhook_payload)
528+
529+
request.metrics.increment(
530+
"warehouse.packaging.services.create_project.typo_squatting",
531+
tags=[f"check_name:{exc.check_name!r}"],
532+
)

0 commit comments

Comments
 (0)