Skip to content

Security: Require challenge host role for challenge setup mutations#5117

Draft
RishabhJain2018 wants to merge 5 commits into
masterfrom
cursor/fix-challenge-mutation-host-check-9812
Draft

Security: Require challenge host role for challenge setup mutations#5117
RishabhJain2018 wants to merge 5 commits into
masterfrom
cursor/fix-challenge-mutation-host-check-9812

Conversation

@RishabhJain2018

@RishabhJain2018 RishabhJain2018 commented Jun 3, 2026

Copy link
Copy Markdown
Member

Summary

Adds authorization checks to challenge setup APIs that previously allowed any authenticated user to mutate leaderboards, splits, and tags.

Changes

  • Add host authorization helpers in apps/challenges/utils.py.
  • Enforce checks on leaderboard, dataset split, phase split, and tag/domain update endpoints.

Security impact

Limits challenge structure mutations to users who belong to a challenge host team (or the specific challenge host for tag updates).

Slack Thread

Open in Web Open in Cursor 

Summary by CodeRabbit

  • Bug Fixes
    • Added centralized authorization checks to gate creation and modification of leaderboards, dataset splits, and challenge phase splits.
    • Ensured endpoints return early with proper error responses when host/permission validations fail.
    • Tightened access for updating challenge tags and domain to require full authentication and host validation.

cursoragent and others added 3 commits June 3, 2026 15:47
Restrict leaderboard, dataset split, phase split, and tag update APIs to
challenge hosts instead of any authenticated user.

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>
Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>
Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds reusable authorization validation helpers to the challenges utility module and integrates them into multiple view endpoints. It centralizes host-permission checks for leaderboard, dataset split, and challenge phase split operations, and tightens access control on the challenge tags endpoint.

Changes

Challenge Host Authorization and Endpoint Integration

Layer / File(s) Summary
Authorization helper functions
apps/challenges/utils.py
Introduces get_challenge_host_required_error, get_challenge_modification_error, and get_leaderboard_modification_error functions that validate user authorization against ChallengeHost records and return 401 DRF error responses when unauthorized.
Endpoint validation integration
apps/challenges/views.py
Imports the authorization helpers and applies early validation to create_leaderboard, create_dataset_split, create_challenge_phase_split, and their corresponding PATCH update paths. Changes update_challenge_tags_and_domain from IsAuthenticatedOrReadOnly to IsAuthenticated and adds modification validation.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding security requirements for challenge host role validation on challenge setup mutation operations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/fix-challenge-mutation-host-check-9812

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/challenges/utils.py`:
- Around line 42-46: The current check uses
ChallengeHost.objects.filter(user=request.user).exists() which treats any
membership row (including PENDING or DENIED) as authorization; update the
predicate to only consider active/accepted memberships (e.g., filter by
ChallengeHost.status in [ChallengeHost.ACCEPTED, ChallengeHost.SELF]) or reuse
the existing challenge-specific host predicate used elsewhere (the same
filter/QuerySet logic you use for challenge host checks) so that only active
hosts pass the guard and unauthorized PENDING/DENIED rows are excluded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 464a0a59-c519-48f6-b83d-b9eafedad71c

📥 Commits

Reviewing files that changed from the base of the PR and between 4ae73db and 93ef71c.

📒 Files selected for processing (2)
  • apps/challenges/utils.py
  • apps/challenges/views.py

Comment thread apps/challenges/utils.py Outdated
Only treat ACCEPTED and SELF ChallengeHost rows as authorization for
challenge setup mutation endpoints.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/challenges/utils.py (1)

99-100: ⚠️ Potential issue | 🟠 Major

Major authorization gap: PATCH allows any host to modify unlinked (orphan) leaderboards
In apps/challenges/utils.py:get_leaderboard_modification_error, when no ChallengePhaseSplit rows exist for the leaderboard_id, challenge_ids is empty and the function returns None:

if not challenge_ids:
    return None

That makes the PATCH endpoint apps/challenges/views.py:get_or_update_leaderboard (challenge/create/leaderboard/<leaderboard_pk>/) effectively permit schema/config updates by any active ChallengeHost (the only other check is ChallengeHost.objects.filter(user=request.user, status__in=[ACCEPTED, SELF]).exists()).

This is reachable because create_leaderboard (.../leaderboard/step_2/) creates standalone Leaderboard records (only schema/config_id) before the leaderboard is linked via ChallengePhaseSplit later in the multi-step flow—so a different host can modify someone else’s unlinked leaderboard if it learns the leaderboard_pk.

Fix: enforce ownership even when unlinked (e.g., add a creator/host-team field on Leaderboard and check it here), or block PATCH until the leaderboard is linked to at least one ChallengePhaseSplit.

Minor: get_leaderboard_modification_error duplicates the ChallengeHost check from get_challenge_host_required_error; reuse the helper to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/challenges/utils.py` around lines 99 - 100,
get_leaderboard_modification_error currently returns None when no
ChallengePhaseSplit rows exist (challenge_ids empty), allowing any ChallengeHost
to PATCH an unlinked Leaderboard; update get_leaderboard_modification_error in
apps/challenges/utils.py so that when challenge_ids is empty it does not permit
modification—either return an authorization error requiring the leaderboard be
linked to at least one ChallengePhaseSplit before PATCHing or verify ownership
by a newly added creator/host-team field on Leaderboard (choose one consistent
approach across the app), and remove the duplicated ChallengeHost logic by
calling the existing get_challenge_host_required_error helper to determine host
permissions.
🧹 Nitpick comments (1)
apps/challenges/utils.py (1)

82-91: 💤 Low value

Consider reusing get_challenge_host_required_error to avoid duplication.

The host membership check duplicates lines 42-45. Reusing the existing helper improves maintainability.

♻️ Suggested refactor
 def get_leaderboard_modification_error(request, leaderboard_pk):
     """
     Return a 401 Response if the user cannot modify the given leaderboard.
     """
-    from hosts.models import ChallengeHost
     from hosts.utils import is_user_a_host_of_challenge
-    from rest_framework import status
-    from rest_framework.response import Response
 
-    if not ChallengeHost.objects.filter(
-        user=request.user,
-        status__in=[ChallengeHost.ACCEPTED, ChallengeHost.SELF],
-    ).exists():
-        return Response(
-            {
-                "error": "Sorry, you are not authorized to perform this operation!"
-            },
-            status=status.HTTP_401_UNAUTHORIZED,
-        )
+    host_error = get_challenge_host_required_error(request)
+    if host_error:
+        return host_error
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/challenges/utils.py` around lines 82 - 91, The authorization block
performing ChallengeHost.objects.filter(...).exists() duplicates existing logic;
replace the manual Response construction with a call to the helper
get_challenge_host_required_error() so the same error payload is reused. Locate
the check that uses ChallengeHost.objects.filter(user=request.user,
status__in=[ChallengeHost.ACCEPTED, ChallengeHost.SELF]).exists() and, when it
fails, return get_challenge_host_required_error() instead of constructing a new
Response; ensure imports/reference to get_challenge_host_required_error are
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/challenges/utils.py`:
- Around line 99-100: get_leaderboard_modification_error currently returns None
when no ChallengePhaseSplit rows exist (challenge_ids empty), allowing any
ChallengeHost to PATCH an unlinked Leaderboard; update
get_leaderboard_modification_error in apps/challenges/utils.py so that when
challenge_ids is empty it does not permit modification—either return an
authorization error requiring the leaderboard be linked to at least one
ChallengePhaseSplit before PATCHing or verify ownership by a newly added
creator/host-team field on Leaderboard (choose one consistent approach across
the app), and remove the duplicated ChallengeHost logic by calling the existing
get_challenge_host_required_error helper to determine host permissions.

---

Nitpick comments:
In `@apps/challenges/utils.py`:
- Around line 82-91: The authorization block performing
ChallengeHost.objects.filter(...).exists() duplicates existing logic; replace
the manual Response construction with a call to the helper
get_challenge_host_required_error() so the same error payload is reused. Locate
the check that uses ChallengeHost.objects.filter(user=request.user,
status__in=[ChallengeHost.ACCEPTED, ChallengeHost.SELF]).exists() and, when it
fails, return get_challenge_host_required_error() instead of constructing a new
Response; ensure imports/reference to get_challenge_host_required_error are
present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fc21ad32-4cc4-4132-9e24-c7f8df7ff2fa

📥 Commits

Reviewing files that changed from the base of the PR and between 93ef71c and 5199180.

📒 Files selected for processing (2)
  • apps/challenges/utils.py
  • apps/challenges/views.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/challenges/views.py

@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.75%. Comparing base (4ae73db) to head (5199180).
⚠️ Report is 7 commits behind head on master.

Files with missing lines Patch % Lines
apps/challenges/utils.py 50.00% 9 Missing ⚠️

❌ Your patch check has failed because the patch coverage (50.00%) is below the target coverage (85.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5117      +/-   ##
==========================================
- Coverage   91.84%   91.75%   -0.10%     
==========================================
  Files          96       96              
  Lines        8207     8225      +18     
==========================================
+ Hits         7538     7547       +9     
- Misses        669      678       +9     
Flag Coverage Δ
backend 95.08% <50.00%> (-0.18%) ⬇️
frontend 87.54% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Accounts & Authentication 97.46% <ø> (ø)
Challenges Management 95.25% <50.00%> (-0.32%) ⬇️
Job Processing 89.82% <ø> (ø)
Participants & Teams 100.00% <ø> (ø)
Challenge Hosts 100.00% <ø> (ø)
Analytics 100.00% <ø> (ø)
Web Interface 100.00% <ø> (ø)
Frontend (Gulp) 87.54% <ø> (ø)
All Models 97.65% <ø> (ø)
All Views 100.00% <ø> (ø)
All Serializers 98.60% <ø> (ø)
Utility Functions 96.23% <50.00%> (-0.52%) ⬇️
Core Configuration 82.35% <ø> (ø)
Files with missing lines Coverage Δ
apps/challenges/views.py 100.00% <ø> (ø)
apps/challenges/utils.py 90.10% <50.00%> (-2.73%) ⬇️
Files with missing lines Coverage Δ
apps/challenges/views.py 100.00% <ø> (ø)
apps/challenges/utils.py 90.10% <50.00%> (-2.73%) ⬇️

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4ae73db...5199180. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants