|
1 | 1 | """Endpoints integrating with Github, Bitbucket, and other webhooks."""
|
2 | 2 |
|
3 |
| -import datetime |
4 | 3 | import hashlib
|
5 | 4 | import hmac
|
6 | 5 | import json
|
|
10 | 9 |
|
11 | 10 | import structlog
|
12 | 11 | from django.shortcuts import get_object_or_404
|
13 |
| -from django.utils import timezone |
14 | 12 | from django.utils.crypto import constant_time_compare
|
15 | 13 | from rest_framework import permissions, status
|
16 | 14 | from rest_framework.exceptions import NotFound, ParseError
|
@@ -74,14 +72,6 @@ class WebhookMixin:
|
74 | 72 | integration = None
|
75 | 73 | integration_type = None
|
76 | 74 | invalid_payload_msg = 'Payload not valid'
|
77 |
| - missing_secret_for_pr_events_msg = dedent( |
78 |
| - """ |
79 |
| - This webhook doesn't have a secret configured. |
80 |
| - For security reasons, webhooks without a secret can't process pull/merge request events. |
81 |
| - For more information, read our blog post: https://blog.readthedocs.com/security-update-on-incoming-webhooks/. |
82 |
| - """ |
83 |
| - ).strip() |
84 |
| - |
85 | 75 | missing_secret_deprecated_msg = dedent(
|
86 | 76 | """
|
87 | 77 | This webhook doesn't have a secret configured.
|
@@ -116,12 +106,9 @@ def post(self, request, project_slug):
|
116 | 106 | except Project.DoesNotExist as exc:
|
117 | 107 | raise NotFound("Project not found") from exc
|
118 | 108 |
|
119 |
| - # Deprecate webhooks without a secret |
| 109 | + # Webhooks without a secret are no longer permitted. |
120 | 110 | # https://blog.readthedocs.com/security-update-on-incoming-webhooks/.
|
121 |
| - now = timezone.now() |
122 |
| - deprecation_date = datetime.datetime(2024, 1, 31, tzinfo=datetime.timezone.utc) |
123 |
| - is_deprecated = now >= deprecation_date |
124 |
| - if is_deprecated and not self.has_secret(): |
| 111 | + if not self.has_secret(): |
125 | 112 | return Response(
|
126 | 113 | {"detail": self.missing_secret_deprecated_msg},
|
127 | 114 | status=HTTP_400_BAD_REQUEST,
|
@@ -418,12 +405,9 @@ def is_payload_valid(self):
|
418 | 405 | See https://developer.github.com/webhooks/securing/.
|
419 | 406 | """
|
420 | 407 | signature = self.request.headers.get(GITHUB_SIGNATURE_HEADER)
|
421 |
| - secret = self.get_integration().secret |
422 |
| - if not secret: |
423 |
| - log.debug('Skipping payload signature validation.') |
424 |
| - return True |
425 | 408 | if not signature:
|
426 | 409 | return False
|
| 410 | + secret = self.get_integration().secret |
427 | 411 | msg = self.request.body.decode()
|
428 | 412 | digest = WebhookMixin.get_digest(secret, msg)
|
429 | 413 | result = hmac.compare_digest(
|
@@ -492,13 +476,6 @@ def handle_webhook(self):
|
492 | 476 |
|
493 | 477 | # Handle pull request events.
|
494 | 478 | if self.project.external_builds_enabled and event == GITHUB_PULL_REQUEST:
|
495 |
| - # Requests from anonymous users are ignored. |
496 |
| - if not integration.secret: |
497 |
| - return Response( |
498 |
| - {"detail": self.missing_secret_for_pr_events_msg}, |
499 |
| - status=HTTP_400_BAD_REQUEST, |
500 |
| - ) |
501 |
| - |
502 | 479 | if action in [
|
503 | 480 | GITHUB_PULL_REQUEST_OPENED,
|
504 | 481 | GITHUB_PULL_REQUEST_REOPENED,
|
@@ -598,10 +575,9 @@ def is_payload_valid(self):
|
598 | 575 | See https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#secret-token.
|
599 | 576 | """
|
600 | 577 | token = self.request.headers.get(GITLAB_TOKEN_HEADER, "")
|
| 578 | + if not token: |
| 579 | + return False |
601 | 580 | secret = self.get_integration().secret
|
602 |
| - if not secret: |
603 |
| - log.debug('Skipping payload signature validation.') |
604 |
| - return True |
605 | 581 | return constant_time_compare(secret, token)
|
606 | 582 |
|
607 | 583 | def get_external_version_data(self):
|
@@ -636,8 +612,6 @@ def handle_webhook(self):
|
636 | 612 | event=event,
|
637 | 613 | )
|
638 | 614 |
|
639 |
| - integration = self.get_integration() |
640 |
| - |
641 | 615 | # Always update `latest` branch to point to the default branch in the repository
|
642 | 616 | # even if the event is not gonna be handled. This helps us to keep our db in sync.
|
643 | 617 | default_branch = self.data.get("project", {}).get("default_branch", None)
|
@@ -665,12 +639,6 @@ def handle_webhook(self):
|
665 | 639 | raise ParseError('Parameter "ref" is required') from exc
|
666 | 640 |
|
667 | 641 | if self.project.external_builds_enabled and event == GITLAB_MERGE_REQUEST:
|
668 |
| - if not integration.secret: |
669 |
| - return Response( |
670 |
| - {"detail": self.missing_secret_for_pr_events_msg}, |
671 |
| - status=HTTP_400_BAD_REQUEST, |
672 |
| - ) |
673 |
| - |
674 | 642 | if action in [
|
675 | 643 | GITLAB_MERGE_REQUEST_OPEN,
|
676 | 644 | GITLAB_MERGE_REQUEST_REOPEN,
|
@@ -779,12 +747,9 @@ def is_payload_valid(self):
|
779 | 747 | See https://support.atlassian.com/bitbucket-cloud/docs/manage-webhooks/#Secure-webhooks.
|
780 | 748 | """
|
781 | 749 | signature = self.request.headers.get(BITBUCKET_SIGNATURE_HEADER)
|
782 |
| - secret = self.get_integration().secret |
783 |
| - if not secret: |
784 |
| - log.debug("Skipping payload signature validation.") |
785 |
| - return True |
786 | 750 | if not signature:
|
787 | 751 | return False
|
| 752 | + secret = self.get_integration().secret |
788 | 753 | msg = self.request.body.decode()
|
789 | 754 | digest = WebhookMixin.get_digest(secret, msg)
|
790 | 755 | result = hmac.compare_digest(
|
|
0 commit comments