BLT icon indicating copy to clipboard operation
BLT copied to clipboard

create a github_webhook url that will update the hackathon PRs

Open DonnieBLT opened this issue 9 months ago • 6 comments

DonnieBLT avatar Mar 19 '25 22:03 DonnieBLT

@coderabbitai please code this

DonnieBLT avatar Mar 19 '25 22:03 DonnieBLT

[!TIP] For best results, initiate chat on the files or code changes.

Unable to process this comment: maximum number of iterations reached.

🧩 Analysis chain

I'll need to examine the issue details and understand the codebase structure before I can code the GitHub webhook for updating hackathon PRs. Let me gather this information first.


🏁 Script executed:

#!/bin/bash
# First, let's check the issue details to understand the requirements
gh issue view 4004 --repo OWASP-BLT/BLT

Length of output: 214


I need to gather more information about the codebase to understand how to implement the GitHub webhook for updating hackathon PRs. Let me examine the project structure first.


🏁 Script executed:

#!/bin/bash
# Get a high-level overview of the project structure
find . -type d -maxdepth 2 | sort

Length of output: 624


Let me check for existing webhook implementations in the codebase and examine the Django project structure.


🏁 Script executed:

#!/bin/bash
# Check for existing webhooks in the codebase
rg -i "webhook" --type py -g "*.py"

Length of output: 3039


Let me examine the existing GitHub webhook implementation in the codebase to understand how I should implement the new one for hackathon PRs.


🏁 Script executed:

#!/bin/bash
# Check the existing github_webhook implementation
cat website/views/user.py | grep -A30 "def github_webhook"

Length of output: 1247


Let me look at how the existing pull request handler is implemented and how hackathon data is stored in the system.


🏁 Script executed:

#!/bin/bash
# Check the implementation of handle_pull_request_event
cat website/views/user.py | grep -A50 "def handle_pull_request_event"

Length of output: 2234


Now I need to check the hackathon-related models in the codebase to understand how hackathon PRs are stored and should be updated.


🏁 Script executed:

#!/bin/bash
# Search for hackathon-related models and functionality
rg -i "hackathon" --type py -g "*.py" | head -50

Length of output: 4278


Let me check the Hackathon model definition and PR tracking implementations.


🏁 Script executed:

#!/bin/bash
# Find the Hackathon model definition
rg -i "class Hackathon\(" --type py -g "*.py"

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Check the Hackathon model definition
cat website/models.py | grep -A50 "class Hackathon"

Length of output: 4981


Let me check how pull requests are tracked in the codebase.


🏁 Script executed:

#!/bin/bash
# Check for PR-related models
rg -i "class PullRequest" --type py -g "*.py"

Length of output: 47


🏁 Script executed:

#!/bin/bash
# More general search for PR-related models
rg -i "pull request" --type py -g "models.py"

Length of output: 495


coderabbitai[bot] avatar Mar 19 '25 22:03 coderabbitai[bot]

@coderabbitai please show the code to create a /github_webhook url / view that will update the /hackathons/ section

DonnieBLT avatar Mar 19 '25 22:03 DonnieBLT

[!TIP] For best results, initiate chat on the files or code changes.

🧩 Analysis chain

I'll gather information about the existing codebase structure to implement the GitHub webhook properly.


🏁 Script executed:

#!/bin/bash
# First, let's check the project structure to understand the codebase organization
find . -type d -maxdepth 2 | sort

Length of output: 624


Now I'll check for existing webhook implementations and understand how the hackathon functionality is structured.


🏁 Script executed:

#!/bin/bash
# Check for existing webhook implementations
rg -i "webhook" --type py -g "*.py" | head -50

Length of output: 3050


🏁 Script executed:

#!/bin/bash
# Look at the existing GitHub webhook implementation
cat website/views/user.py | grep -A50 "def github_webhook"

Length of output: 2161


🏁 Script executed:

#!/bin/bash
# Check hackathon models to understand what needs to be updated
cat website/models.py | grep -A50 "class Hackathon"

Length of output: 4981


Let me check if there are any existing hackathon PR or participant models.


🏁 Script executed:

#!/bin/bash
# Check for hackathon participant/PR related models
rg -i "participant|pull request|PR" website/models.py | head -50

Length of output: 2607


Let me check the URL patterns and how the project is set up to connect PRs with hackathons.


🏁 Script executed:

#!/bin/bash
# Check URL configuration and hackathon-related paths
cat blt/urls.py | grep -A5 "github_webhook\|hackathon"

Length of output: 1672


🏁 Script executed:

#!/bin/bash
# Check if there's a model for tracking hackathon participants/pull requests
rg -i "hackathonparticipant|hackathonpr" --type py

Length of output: 1175


Let me gather more information about how repositories and PRs are tracked in the system.


🏁 Script executed:

#!/bin/bash
# Check for Repo model definition
cat website/models.py | grep -A20 "class Repo" 

Length of output: 1246


🏁 Script executed:

#!/bin/bash
# Check for any existing PR tracking model and the handle_pull_request_event function implementation
rg -i "class PullRequest" --type py

# Check the handle_pull_request_event function implementation
cat website/views/user.py | grep -A30 "def handle_pull_request_event"

Length of output: 1460


@DonnieBLT I'll create the code for a GitHub webhook that will update the hackathon PRs. Based on my analysis of your codebase, I'll need to:

  1. Create a model to track hackathon pull requests
  2. Enhance the existing GitHub webhook handler

Here's the implementation:

First, add a new model in website/models.py:

class HackathonPR(models.Model):
    hackathon = models.ForeignKey(Hackathon, on_delete=models.CASCADE, related_name="pull_requests")
    repo = models.ForeignKey(Repo, on_delete=models.CASCADE, related_name="hackathon_prs")
    pr_number = models.IntegerField()
    pr_title = models.CharField(max_length=255)
    pr_url = models.URLField()
    author_url = models.URLField()
    author_name = models.CharField(max_length=100)
    author_avatar = models.URLField(null=True, blank=True)
    state = models.CharField(max_length=20, choices=[
        ('open', 'Open'),
        ('closed', 'Closed'),
        ('merged', 'Merged')
    ])
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()
    
    class Meta:
        unique_together = ('repo', 'pr_number')
        ordering = ['-updated_at']
        
    def __str__(self):
        return f"PR #{self.pr_number}: {self.pr_title} ({self.repo.name})"

Next, modify the handle_pull_request_event function in website/views/user.py:

def handle_pull_request_event(payload):
    # Get PR details from payload
    pr_data = payload["pull_request"]
    repo_url = pr_data["base"]["repo"]["html_url"]
    pr_number = pr_data["number"]
    pr_title = pr_data["title"]
    pr_url = pr_data["html_url"]
    author_url = pr_data["user"]["html_url"]
    author_name = pr_data["user"]["login"]
    author_avatar = pr_data["user"]["avatar_url"]
    created_at = datetime.fromisoformat(pr_data["created_at"].replace("Z", "+00:00"))
    updated_at = datetime.fromisoformat(pr_data["updated_at"].replace("Z", "+00:00"))
    
    # Determine PR state
    state = "open"
    if pr_data["state"] == "closed":
        state = "merged" if pr_data["merged"] else "closed"
    
    # Check if the repo is associated with any active hackathons
    try:
        repo = Repo.objects.get(repo_url=repo_url)
        hackathons = repo.hackathons.filter(is_active=True)
        
        # Update or create PR records for each associated hackathon
        for hackathon in hackathons:
            HackathonPR.objects.update_or_create(
                repo=repo,
                pr_number=pr_number,
                hackathon=hackathon,
                defaults={
                    'pr_title': pr_title,
                    'pr_url': pr_url,
                    'author_url': author_url,
                    'author_name': author_name,
                    'author_avatar': author_avatar,
                    'state': state,
                    'created_at': created_at,
                    'updated_at': updated_at
                }
            )
            
        # Log the update
        if hackathons.exists():
            logger.info(f"Updated PR #{pr_number} for {hackathons.count()} hackathons")
            
    except Repo.DoesNotExist:
        logger.warning(f"Repository not found for URL: {repo_url}")
    except Exception as e:
        logger.error(f"Error processing PR event: {str(e)}")
    
    # Continue with the existing badge assignment functionality
    if payload["action"] == "closed" and pr_data["merged"]:
        pr_user_profile = UserProfile.objects.filter(github_url=author_url).first()
        if pr_user_profile:
            pr_user_instance = pr_user_profile.user
            assign_github_badge(pr_user_instance, "First PR Merged")
            
    return JsonResponse({"status": "success"}, status=200)

Add these imports at the top of website/views/user.py:

from website.models import HackathonPR, Repo
from datetime import datetime
import logging

logger = logging.getLogger(__name__)

After implementing these changes, the GitHub webhook will track and update pull requests related to active hackathons, keeping the /hackathons/ section current with PR activity.

coderabbitai[bot] avatar Mar 19 '25 22:03 coderabbitai[bot]

/giphy hackathon

DonnieBLT avatar Mar 20 '25 07:03 DonnieBLT

Giphy GIF

github-actions[bot] avatar Mar 20 '25 07:03 github-actions[bot]

@coderabbitai can you check if copilot or any other contributor is working on this?

Nachiket-Roy avatar Dec 02 '25 17:12 Nachiket-Roy

Implementation Plan

Approach

Ticket Overview

The existing GitHub webhook endpoint needs to properly update pull request data in the database so that hackathon leaderboards can accurately track merged PRs during hackathon timeframes. Currently, the webhook handles PR events but doesn't update the GitHubIssue fields that hackathons rely on.

Codebase Summary

This is a Django-based platform with an existing webhook infrastructure at /github-webhook/ that processes GitHub events. The GitHubIssue model tracks both issues and pull requests with fields for merge status and timestamps. Hackathons query merged PRs filtered by date ranges to build leaderboards. The current handle_pull_request_event() only assigns badges but doesn't update PR state, while the issue event handler demonstrates the proper pattern of updating GitHubIssue fields from webhook payloads.

Observations

This is a Django-based platform with an existing webhook infrastructure at /github-webhook/ that processes GitHub events. The GitHubIssue model tracks both issues and pull requests with fields for merge status and timestamps. Hackathons query merged PRs filtered by date ranges to build leaderboards. The current handle_pull_request_event() only assigns badges but doesn't update PR state, while the issue event handler demonstrates the proper pattern of updating GitHubIssue fields from webhook payloads.

Plan

Phase 1: Pull Request Event Handling

Enhance the PR webhook handler to properly track pull request lifecycle events and update database records that hackathons depend on for leaderboard generation.

Task 1: Extend PR Event Handler to Process Multiple Actions

Update the handle_pull_request_event() function in website/views/user.py (currently at lines 1233-1239) to handle PR lifecycle events beyond just merged closures

  • Replace the current single-action handler with a multi-action dispatcher similar to handle_issue_event()
  • Add action-based conditional logic to handle "opened", "closed", "reopened", and "synchronize" actions from the webhook payload
  • For "closed" action, extract the pull_request.merged boolean from the payload to differentiate merged vs. unmerged PR closes
  • Extract PR metadata from webhook payload structure: pull_request.state, pull_request.merged_at, pull_request.closed_at, and pull_request.updated_at
  • Retrieve or create the GitHubIssue record using pull_request.number as issue_id and the repository information
Task 2: Update GitHubIssue Fields from PR Events

Extend the handle_pull_request_event() function in website/views/user.py to persist PR state changes to the GitHubIssue model

  • Update the state field on the GitHubIssue instance based on PR action: "open" for opened/reopened, "closed" for closed actions
  • Set is_merged field to True when action is "closed" AND payload's pull_request.merged is True; otherwise set to False
  • Parse and save the merged_at timestamp from pull_request.merged_at when PR is merged (convert ISO format string to Django datetime using dateutil.parser.parse())
  • Update the closed_at field from pull_request.closed_at when PR is closed (whether merged or not)
  • Update the updated_at field from pull_request.updated_at for all actions to track last modification time
  • Preserve the existing badge assignment logic for merged PRs (the current functionality)
  • Use the same field update and save pattern demonstrated in handle_issue_event() at lines 1262-1295 for consistency
🤖 Prompt for AI agents
Enhance the PR webhook handler to track pull request lifecycle events and update
database records for hackathon leaderboards.

In `website/views/user.py`, update the `handle_pull_request_event()` function:
- Replace single-action handler with multi-action dispatcher similar to
`handle_issue_event()`
- Add conditional logic for "opened", "closed", "reopened", and "synchronize"
actions
- Extract PR metadata from payload: `pull_request.state`, `pull_request.merged`,
`pull_request.merged_at`, `pull_request.closed_at`, `pull_request.updated_at`
- Retrieve or create GitHubIssue record using `pull_request.number` and
repository information
- Update `state` field: "open" for opened/reopened, "closed" for closed actions
- Set `is_merged` to True when action is "closed" AND `pull_request.merged` is
True
- Parse and save `merged_at` timestamp using `dateutil.parser.parse()` when PR
is merged
- Update `closed_at` field when PR is closed
- Update `updated_at` field for all actions
- Preserve existing badge assignment logic for merged PRs
- Follow the same field update pattern as `handle_issue_event()`

Phase 2: Webhook Security Implementation

Add signature verification to ensure webhook requests authentically originate from GitHub, addressing the security gap documented in the codebase.

Task 1: Implement HMAC Signature Validation Function

Create a new validate_github_signature() helper function in website/views/user.py before the github_webhook() function

  • Function signature: validate_github_signature(payload_body: bytes, signature_header: str, secret: str) -> bool
  • Implement HMAC-SHA256 algorithm using Python's hmac module with the webhook secret
  • Calculate the expected signature by computing hmac.new(secret.encode(), payload_body, hashlib.sha256).hexdigest()
  • Prepend "sha256=" to the calculated signature to match GitHub's header format
  • Compare the calculated signature against the signature_header value using hmac.compare_digest() for constant-time comparison (prevents timing attacks)
  • Return True if signatures match, False otherwise
  • Handle edge cases: return False if signature_header is None or empty
Task 2: Enable Signature Verification in Webhook Handler

Update the github_webhook() function in website/views/user.py (lines 1215-1231) to enforce signature validation

  • Uncomment the signature validation block (lines 1218-1220) that extracts the X-Hub-Signature-256 header
  • Replace the commented call to non-existent validate_signature() with a call to the new validate_github_signature() function
  • Pass three arguments to validation function: request.body (raw payload bytes), signature header value, and webhook secret from settings
  • Return JsonResponse({"status": "error", "message": "Invalid signature"}, status=403) if validation fails
  • Add a new Django setting GITHUB_WEBHOOK_SECRET to blt/settings.py (use os.environ.get('GITHUB_WEBHOOK_SECRET') for production security)
  • Add documentation comment above the signature check explaining the security measure
🤖 Prompt for AI agents
Add HMAC signature verification to ensure webhook requests authentically
originate from GitHub.

In `website/views/user.py`, create `validate_github_signature()` helper function
before `github_webhook()`:
- Function signature: `validate_github_signature(payload_body: bytes,
signature_header: str, secret: str) -> bool`
- Calculate expected signature using HMAC-SHA256: `hmac.new(secret.encode(),
payload_body, hashlib.sha256).hexdigest()`
- Prepend "sha256=" to match GitHub's format
- Use `hmac.compare_digest()` for constant-time comparison
- Return False for None or empty signature_header

Update `github_webhook()` function:
- Uncomment signature validation block that extracts `X-Hub-Signature-256`
header
- Call `validate_github_signature()` with `request.body`, signature header, and
webhook secret from settings
- Return 403 JsonResponse with error message if validation fails
- Add documentation comment explaining security measure

In `blt/settings.py`:
- Add `GITHUB_WEBHOOK_SECRET` setting using
`os.environ.get('GITHUB_WEBHOOK_SECRET')`

Phase 3: Test Coverage for PR Events

Add comprehensive test cases to validate PR event handling and security implementation, following the existing test patterns in website/tests/test_github_webhook.py.

Task 1: Add Pull Request Event Tests

Create new test methods in website/tests/test_github_webhook.py following the existing test class structure

  • Create test_webhook_handles_pr_opened(): Send PR opened webhook payload, verify GitHubIssue is created with type="pull_request", state="open", and is_merged=False
  • Create test_webhook_handles_pr_merged(): Send PR closed webhook payload with merged=true, verify is_merged=True, merged_at timestamp is saved, and state="closed"
  • Create test_webhook_handles_pr_closed_unmerged(): Send PR closed webhook payload with merged=false, verify is_merged=False, merged_at=None, and state="closed"
  • Create test_webhook_handles_pr_reopened(): Send PR reopened webhook payload, verify state="open" and merge fields remain unchanged
  • Use the existing test fixture pattern: create test Repo and UserProfile instances in setUp(), construct GitHub-formatted JSON payloads matching actual webhook structure
  • Follow assertion patterns from existing tests: verify database state changes using GitHubIssue.objects.get() and field comparisons
Task 2: Add Signature Validation Tests

Create new test methods in website/tests/test_github_webhook.py for webhook security validation

  • Create test_webhook_rejects_invalid_signature(): Send webhook with incorrect HMAC signature, verify response is 403 with error message
  • Create test_webhook_rejects_missing_signature(): Send webhook without X-Hub-Signature-256 header, verify response is 403
  • Create test_webhook_accepts_valid_signature(): Send webhook with correctly calculated HMAC-SHA256 signature, verify response is 200 and event is processed
  • Use @override_settings(GITHUB_WEBHOOK_SECRET='test-secret') decorator to set test secret without affecting other tests
  • Calculate test signatures using the same HMAC-SHA256 algorithm that will be implemented in validate_github_signature()
  • Mock or patch the signature validation function in some tests to isolate security logic from event processing logic
🤖 Prompt for AI agents
Add comprehensive test cases for PR event handling and webhook security.

In `website/tests/test_github_webhook.py`, create PR event tests:
- `test_webhook_handles_pr_opened()`: Verify GitHubIssue created with
`type="pull_request"`, `state="open"`, `is_merged=False`
- `test_webhook_handles_pr_merged()`: Verify `is_merged=True`, `merged_at`
timestamp saved, `state="closed"`
- `test_webhook_handles_pr_closed_unmerged()`: Verify `is_merged=False`,
`merged_at=None`, `state="closed"`
- `test_webhook_handles_pr_reopened()`: Verify `state="open"` and merge fields
unchanged
- Use existing test fixture pattern with test Repo and UserProfile in setUp()
- Construct GitHub-formatted JSON payloads matching actual webhook structure
- Verify database state using `GitHubIssue.objects.get()` and field comparisons

Create signature validation tests:
- `test_webhook_rejects_invalid_signature()`: Send webhook with incorrect HMAC,
verify 403 response
- `test_webhook_rejects_missing_signature()`: Send webhook without
`X-Hub-Signature-256` header, verify 403
- `test_webhook_accepts_valid_signature()`: Send webhook with correct
HMAC-SHA256 signature, verify 200 and processing
- Use `@override_settings(GITHUB_WEBHOOK_SECRET='test-secret')` decorator
- Calculate test signatures using HMAC-SHA256 algorithm
- Mock or patch signature validation to isolate security logic from event
processing

🚀 Next Steps

🤖 All AI agent prompts combined
Task: 1

Enhance the PR webhook handler to track pull request lifecycle events and update
database records for hackathon leaderboards.

In `website/views/user.py`, update the `handle_pull_request_event()` function:
- Replace single-action handler with multi-action dispatcher similar to
`handle_issue_event()`
- Add conditional logic for "opened", "closed", "reopened", and "synchronize"
actions
- Extract PR metadata from payload: `pull_request.state`, `pull_request.merged`,
`pull_request.merged_at`, `pull_request.closed_at`, `pull_request.updated_at`
- Retrieve or create GitHubIssue record using `pull_request.number` and
repository information
- Update `state` field: "open" for opened/reopened, "closed" for closed actions
- Set `is_merged` to True when action is "closed" AND `pull_request.merged` is
True
- Parse and save `merged_at` timestamp using `dateutil.parser.parse()` when PR
is merged
- Update `closed_at` field when PR is closed
- Update `updated_at` field for all actions
- Preserve existing badge assignment logic for merged PRs
- Follow the same field update pattern as `handle_issue_event()`
===============================================================================

Task: 2

Add HMAC signature verification to ensure webhook requests authentically
originate from GitHub.

In `website/views/user.py`, create `validate_github_signature()` helper function
before `github_webhook()`:
- Function signature: `validate_github_signature(payload_body: bytes,
signature_header: str, secret: str) -> bool`
- Calculate expected signature using HMAC-SHA256: `hmac.new(secret.encode(),
payload_body, hashlib.sha256).hexdigest()`
- Prepend "sha256=" to match GitHub's format
- Use `hmac.compare_digest()` for constant-time comparison
- Return False for None or empty signature_header

Update `github_webhook()` function:
- Uncomment signature validation block that extracts `X-Hub-Signature-256`
header
- Call `validate_github_signature()` with `request.body`, signature header, and
webhook secret from settings
- Return 403 JsonResponse with error message if validation fails
- Add documentation comment explaining security measure

In `blt/settings.py`:
- Add `GITHUB_WEBHOOK_SECRET` setting using
`os.environ.get('GITHUB_WEBHOOK_SECRET')`
===============================================================================

Task: 3

Add comprehensive test cases for PR event handling and webhook security.

In `website/tests/test_github_webhook.py`, create PR event tests:
- `test_webhook_handles_pr_opened()`: Verify GitHubIssue created with
`type="pull_request"`, `state="open"`, `is_merged=False`
- `test_webhook_handles_pr_merged()`: Verify `is_merged=True`, `merged_at`
timestamp saved, `state="closed"`
- `test_webhook_handles_pr_closed_unmerged()`: Verify `is_merged=False`,
`merged_at=None`, `state="closed"`
- `test_webhook_handles_pr_reopened()`: Verify `state="open"` and merge fields
unchanged
- Use existing test fixture pattern with test Repo and UserProfile in setUp()
- Construct GitHub-formatted JSON payloads matching actual webhook structure
- Verify database state using `GitHubIssue.objects.get()` and field comparisons

Create signature validation tests:
- `test_webhook_rejects_invalid_signature()`: Send webhook with incorrect HMAC,
verify 403 response
- `test_webhook_rejects_missing_signature()`: Send webhook without
`X-Hub-Signature-256` header, verify 403
- `test_webhook_accepts_valid_signature()`: Send webhook with correct
HMAC-SHA256 signature, verify 200 and processing
- Use `@override_settings(GITHUB_WEBHOOK_SECRET='test-secret')` decorator
- Calculate test signatures using HMAC-SHA256 algorithm
- Mock or patch signature validation to isolate security logic from event
processing
💡 Iterate on the plan with: @coderabbitai <feedback>
Example Feedback
- @coderabbitai You can skip phase 3. Add a simple unit test case for phase 2.
- @coderabbitai For assumption 1 go ahead with option 3 and replan.

coderabbitai[bot] avatar Dec 02 '25 17:12 coderabbitai[bot]

@coderabbitai could you check whether Copilot or any other contributor is already assigned or actively working on this implementation plan?

Nachiket-Roy avatar Dec 02 '25 18:12 Nachiket-Roy

🧩 Analysis chain

@Nachiket-Roy Let me check the current status of issue #4004 to see if anyone is assigned or actively working on it.


🏁 Script executed:

#!/bin/bash
# Check the issue details including assignees and current status
gh issue view 4004 --repo OWASP-BLT/BLT --json assignees,state,title,labels,comments --jq '{assignees: .assignees[].login, state: .state, title: .title, labels: .labels[].name, recent_comments: .comments[-5:] | .[] | {author: .author.login, body: .body, createdAt: .createdAt}}'

# Check for any linked pull requests to this issue
gh pr list --repo OWASP-BLT/BLT --search "4004" --json number,title,author,state,createdAt --jq '.[] | {number: .number, title: .title, author: .author.login, state: .state, createdAt: .createdAt}'

Length of output: 0


Let me try with simpler commands to check the issue status:


🏁 Script executed:

#!/bin/bash
# Get basic issue information
gh issue view 4004 --repo OWASP-BLT/BLT

echo "---CHECKING FOR LINKED PRS---"
# Search for PRs that mention this issue
gh pr list --repo OWASP-BLT/BLT --state all --limit 50 --json number,title,author,state,body | jq '.[] | select(.body | contains("#4004") or contains("4004"))'

echo "---CHECKING RECENT ACTIVITY---"
# Get the last few comments on the issue
gh issue view 4004 --repo OWASP-BLT/BLT --comments --json comments | jq '.comments[-10:] | .[] | {author: .author.login, created: .createdAt, body: .body[:200]}'

Length of output: 2183


@Nachiket-Roy Based on my check of issue #4004, I can confirm:

No one is currently assigned or working on this issue

Here's what I found:

  • Assignees: None
  • Linked Pull Requests: None found
  • Labels: good first issue, gsoc, help wanted - indicating it's available for contributors
  • Recent Activity: Only the current conversation thread; no comments from other contributors claiming to work on it

The issue is open and available for anyone to pick up. Since you're interested in having this implemented, would you like to:

  1. Assign it to a specific contributor who can work on the implementation plan I provided?
  2. Keep it open for community contributors to claim?
  3. Have me proceed with creating the code changes based on the implementation plan?

coderabbitai[bot] avatar Dec 02 '25 18:12 coderabbitai[bot]

@coderabbitai Can you explain the issue to me and a implementation plan on it?

Jayant2908 avatar Dec 04 '25 07:12 Jayant2908

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the full issue details including description and context
gh issue view 4004 --repo OWASP-BLT/BLT --json title,body,labels,createdAt,author --jq '{title: .title, body: .body, labels: .labels[].name, created: .createdAt, author: .author.login}'

Length of output: 483


🏁 Script executed:

#!/bin/bash
# Get context on existing webhook implementation
echo "=== EXISTING WEBHOOK HANDLER ==="
rg -A 20 "def github_webhook" website/views/user.py

echo -e "\n=== HANDLE_PULL_REQUEST_EVENT ==="
rg -A 30 "def handle_pull_request_event" website/views/user.py

echo -e "\n=== GITHUBISSUE MODEL ==="
rg -A 30 "class GitHubIssue" website/models.py | head -40

echo -e "\n=== HACKATHON MODEL ==="
rg -A 25 "class Hackathon\(" website/models.py

Length of output: 4748


@Jayant2908

📋 Issue Explanation

Issue #4004: Create a GitHub webhook URL that will update the hackathon PRs

The Problem

The BLT platform runs hackathons where participants submit pull requests to designated repositories. Currently, while there's a webhook endpoint at /github-webhook/ that receives PR events from GitHub, it only assigns badges to users but doesn't track PR data in the database. This means:

  • ❌ Hackathon leaderboards can't show real-time PR statistics
  • ❌ PR lifecycle events (opened, merged, closed, reopened) aren't being recorded
  • ❌ No way to filter PRs by date range for specific hackathons
  • ❌ Security vulnerability: webhook signature validation is disabled

Current State

def handle_pull_request_event(payload):
    # Currently only assigns badges, doesn't save PR data
    if payload["action"] == "closed" and payload["pull_request"]["merged"]:
        assign_github_badge(pr_user_instance, "First PR Merged")
    return JsonResponse({"status": "success"}, status=200)

What Needs to Happen

The webhook should persist PR data to the GitHubIssue model (which already has fields for is_merged, merged_at, state, etc.) so that hackathons can query merged PRs within their date ranges.


🎯 Implementation Plan

Phase 1: Pull Request Event Handling

Goal: Track PR lifecycle events in the database

Task 1.1: Extend PR Event Handler

Update handle_pull_request_event() in website/views/user.py to:

  • Process multiple actions: "opened", "closed", "reopened"
  • Extract PR metadata from webhook payload:
    • pr_number, title, body, url
    • created_at, updated_at, closed_at, merged_at
    • state (open/closed), merged (true/false)
    • Repository and user information

Task 1.2: Update GitHubIssue Records

Persist PR state changes to database:

# Pseudo-code for the implementation
def handle_pull_request_event(payload):
    pr_data = payload["pull_request"]
    action = payload["action"]
    
    # Get or create GitHubIssue record
    github_issue, created = GitHubIssue.objects.update_or_create(
        issue_id=pr_data["id"],
        repo=repo,
        defaults={
            'type': 'pull_request',
            'title': pr_data["title"],
            'state': pr_data["state"],  # 'open' or 'closed'
            'is_merged': pr_data.get("merged", False),
            'merged_at': pr_data.get("merged_at"),  # Critical for hackathon filtering
            'closed_at': pr_data.get("closed_at"),
            'updated_at': pr_data["updated_at"],
            # ... other fields
        }
    )

Key Database Fields to Update:

  • state: "open" or "closed"
  • is_merged: True when PR is merged
  • merged_at: Timestamp (critical for hackathon date filtering)
  • closed_at, updated_at: Maintain accurate timestamps

Phase 2: Webhook Security Implementation

Goal: Secure the webhook endpoint (currently disabled)

Task 2.1: Implement HMAC Signature Validation

Create validate_github_signature() function:

import hmac
import hashlib

def validate_github_signature(payload_body, signature_header):
    """Validate GitHub webhook signature using HMAC-SHA256"""
    secret = settings.GITHUB_WEBHOOK_SECRET
    expected_signature = 'sha256=' + hmac.new(
        secret.encode(),
        payload_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_signature, signature_header)

Task 2.2: Enable Signature Verification

Uncomment and fix the validation in github_webhook():

def github_webhook(request):
    if request.method == "POST":
        signature = request.headers.get("X-Hub-Signature-256")
        if not validate_github_signature(request.body, signature):
            return JsonResponse({"status": "error", "message": "Unauthorized"}, status=403)
        # ... rest of handler

Add to Django settings:

GITHUB_WEBHOOK_SECRET = os.environ.get('GITHUB_WEBHOOK_SECRET', '')

Phase 3: Test Coverage

Goal: Ensure reliability with comprehensive tests

Task 3.1: PR Event Tests

Create tests in website/tests/test_github_webhook.py:

  • Test PR opened creates GitHubIssue with type="pull_request", state="open"
  • Test PR merged sets is_merged=True and saves merged_at timestamp
  • Test PR closed (not merged) sets is_merged=False
  • Test PR reopened updates state back to "open"

Task 3.2: Security Tests

  • Test invalid signature returns 403
  • Test missing signature returns 403
  • Test valid HMAC-SHA256 signature returns 200

📦 Expected Outcome

After implementation:

  1. ✅ PRs on hackathon repos are tracked in real-time
  2. ✅ Hackathon leaderboards can query merged PRs by date range
  3. ✅ Webhook endpoint is secured with HMAC verification
  4. ✅ Comprehensive test coverage ensures reliability

🚀 Getting Started

  1. Start with Phase 1, Task 1 - understanding the existing handle_issue_event() pattern
  2. Study the GitHubIssue model structure
  3. Test locally with GitHub webhook payload samples
  4. Add security in Phase 2 before production deployment

Would you like me to provide more details on any specific part of the implementation?

coderabbitai[bot] avatar Dec 04 '25 07:12 coderabbitai[bot]

/assign

Jayant2908 avatar Dec 04 '25 07:12 Jayant2908

Hello @Jayant2908! You've been assigned to OWASP-BLT/BLT issue #4004. You have 24 hours to complete a pull request.


This comment was generated by OWASP BLT-Action

github-actions[bot] avatar Dec 04 '25 07:12 github-actions[bot]

⏰ This issue has been automatically unassigned from Jayant2908 due to 24 hours of inactivity. The issue is now available for anyone to work on again.


This comment was generated by OWASP BLT-Action

github-actions[bot] avatar Dec 05 '25 07:12 github-actions[bot]

Working on it!

Jayant2908 avatar Dec 05 '25 08:12 Jayant2908

@coderabbitai Can you make sure this has not yet been implemented?

Jayant2908 avatar Dec 07 '25 09:12 Jayant2908

🧩 Analysis chain

@Jayant2908 Let me check the current state of the implementation to verify if this has been completed.


🏁 Script executed:

#!/bin/bash
# Check the current implementation of handle_pull_request_event
echo "=== CURRENT HANDLE_PULL_REQUEST_EVENT IMPLEMENTATION ==="
cat website/views/user.py | grep -A 40 "def handle_pull_request_event"

echo -e "\n=== CHECK FOR SIGNATURE VALIDATION ==="
cat website/views/user.py | grep -A 20 "def validate_github_signature"

echo -e "\n=== CHECK GITHUB_WEBHOOK FUNCTION ==="
cat website/views/user.py | grep -A 30 "def github_webhook"

echo -e "\n=== CHECK FOR WEBHOOK TESTS ==="
fd test_github_webhook.py website/tests/

echo -e "\n=== CHECK FOR RECENT PRs MENTIONING THIS ISSUE ==="
gh pr list --repo OWASP-BLT/BLT --state all --limit 20 --json number,title,body,state,mergedAt | jq '.[] | select(.body | tostring | contains("4004"))'

echo -e "\n=== CHECK IF GITHUBISSUE IS BEING UPDATED IN PR HANDLER ==="
cat website/views/user.py | grep -A 50 "def handle_pull_request_event" | grep -i "GitHubIssue\|update_or_create\|is_merged\|merged_at"

Length of output: 3200


🏁 Script executed:

#!/bin/bash
# Check what's currently in the webhook test file
echo "=== CURRENT WEBHOOK TEST FILE CONTENTS ==="
cat website/tests/test_github_webhook.py

echo -e "\n=== CHECK FOR ANY PR-RELATED TESTS ==="
rg -i "pull.*request|pr.*merged|is_merged" website/tests/test_github_webhook.py

Length of output: 10388


@Jayant2908

Confirmed: Issue #4004 has NOT been implemented yet

Here's what I verified:

Current State of the Codebase:

❌ Phase 1 (PR Event Handling) - NOT Implemented

def handle_pull_request_event(payload):
    # Only assigns badges, doesn't save PR data!
    if payload["action"] == "closed" and payload["pull_request"]["merged"]:
        pr_user_profile = UserProfile.objects.filter(github_url=payload["pull_request"]["user"]["html_url"]).first()
        if pr_user_profile:
            pr_user_instance = pr_user_profile.user
            assign_github_badge(pr_user_instance, "First PR Merged")
    return JsonResponse({"status": "success"}, status=200)
  • ❌ No GitHubIssue.objects.update_or_create() calls
  • ❌ Not tracking is_merged, merged_at, state, or any PR metadata
  • ❌ Only processes "closed" action, ignores "opened" and "reopened"

❌ Phase 2 (Webhook Security) - NOT Implemented

def github_webhook(request):
    # Signature validation is commented out!
    # signature = request.headers.get("X-Hub-Signature-256")
    # if not validate_signature(request.body, signature):
    #    return JsonResponse({"status": "error", "message": "Unauthorized request"}, status=403)
  • validate_github_signature() function doesn't exist
  • ❌ Signature validation commented out with note "doesn't seem to work?"

❌ Phase 3 (Test Coverage) - NOT Implemented

  • test_github_webhook.py only has tests for issue events
  • ❌ Zero PR-related tests (no PR opened, merged, reopened tests)
  • ❌ Zero signature validation tests

✅ No Conflicting PRs

  • No open or merged PRs found that mention issue #4004

You're good to go! The issue is completely unimplemented and ready for you to work on. All three phases from the implementation plan I provided earlier are still needed. Good luck! 🚀

coderabbitai[bot] avatar Dec 07 '25 09:12 coderabbitai[bot]

@coderabbitai can you code these phases?

Jayant2908 avatar Dec 07 '25 11:12 Jayant2908

I've updated the plan above based on your feedback. Ready to proceed when you are!

coderabbitai[bot] avatar Dec 07 '25 11:12 coderabbitai[bot]

I've updated the plan above based on your feedback. Ready to proceed when you are!

coderabbitai[bot] avatar Dec 07 '25 11:12 coderabbitai[bot]

@coderabbitai I want you give the codes for the phases u mentioned above again now.

Jayant2908 avatar Dec 07 '25 11:12 Jayant2908

I've updated the plan above based on your feedback. Ready to proceed when you are!

coderabbitai[bot] avatar Dec 07 '25 11:12 coderabbitai[bot]