Skip to content

Commit 83480ff

Browse files
ahmedxgoudakasyaarkid15r
authored
Implement Dashboard Auth logic (#1823)
* Add is_owasp_staff to the GitHub user model * Add dashboard wrapper * Protect Dashboard from frontend * Add dashboard button * undo comments * Add wrappers * Refactor and handle loading * Improve button style * Fix session user type * Update backend tests * Update projects wrapper * Use useDjangoSession * Protect backend * Update DashboardWrapper * Update tests * Update docker * Undo docker * Undo docker * Mock auth * Update e2e tests * Update e2e * Update permissions for pdf * Update playwright config with mock GitHub client ID and secret * Update permissions * Update playwright config * Update timeout * Update .env.example and timeout * Update e2e login test * Update Session.user type * Refactor and merge migrations * Apply suggestions * Update tests * Update code * Add credentials * Remove comments from permissions.py * Apply suggestions * Update unit tests * Update e2e * Apply rabbit suggestions * Update code --------- Co-authored-by: Kate Golovanova <[email protected]> Co-authored-by: Arkadii Yakovets <[email protected]> Co-authored-by: Arkadii Yakovets <[email protected]>
1 parent 0079230 commit 83480ff

35 files changed

+424
-190
lines changed

backend/apps/github/api/internal/nodes/user.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"followers_count",
1818
"following_count",
1919
"id",
20+
"is_owasp_staff",
2021
"location",
2122
"login",
2223
"name",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 5.2.4 on 2025-07-23 08:20
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("github", "0032_user_github_user_created_at_desc_and_more"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="user",
14+
name="is_owasp_staff",
15+
field=models.BooleanField(
16+
default=False,
17+
help_text="Indicates if the user is an OWASP staff member.",
18+
verbose_name="OWASP Staff",
19+
),
20+
),
21+
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Generated by Django 5.2.4 on 2025-08-04 18:17
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("github", "0033_alter_release_published_at"),
9+
("github", "0033_user_is_owasp_staff"),
10+
]
11+
12+
operations = []

backend/apps/github/models/user.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ class Meta:
3535

3636
is_bot = models.BooleanField(verbose_name="Is bot", default=False)
3737

38+
is_owasp_staff = models.BooleanField(
39+
default=False,
40+
verbose_name="OWASP Staff",
41+
help_text="Indicates if the user is an OWASP staff member.",
42+
)
43+
3844
contributions_count = models.PositiveIntegerField(
3945
verbose_name="Contributions count", default=0
4046
)

backend/apps/nest/api/internal/mutations/user.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from strawberry.types import Info
1111

1212
from apps.github.models import User as GithubUser
13+
from apps.nest.api.internal.nodes.user import AuthUserNode
1314
from apps.nest.models import User
1415

1516
logger = logging.getLogger(__name__)
@@ -30,6 +31,7 @@ class GitHubAuthResult:
3031

3132
message: str
3233
ok: bool
34+
user: AuthUserNode | None = None
3335

3436

3537
@strawberry.type
@@ -85,6 +87,7 @@ def github_auth(self, info: Info, access_token: str) -> GitHubAuthResult:
8587
return GitHubAuthResult(
8688
message="Successfully authenticated with GitHub.",
8789
ok=True,
90+
user=nest_user,
8891
)
8992
except BadCredentialsException:
9093
return GitHubAuthResult(

backend/apps/nest/api/internal/nodes/user.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@
88
@strawberry_django.type(User, fields=["username"])
99
class AuthUserNode:
1010
"""GraphQL node for User model."""
11+
12+
@strawberry_django.field
13+
def is_owasp_staff(self) -> bool:
14+
"""Check if the user is an OWASP staff member."""
15+
return self.github_user.is_owasp_staff if self.github_user else False

backend/apps/owasp/api/internal/permissions/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Strawberry Permission Classes for Project Health Metrics."""
2+
3+
from strawberry.permission import BasePermission
4+
5+
6+
class HasDashboardAccess(BasePermission):
7+
"""Permission class to check if the user has dashboard access."""
8+
9+
message = "You must have dashboard access to access this resource."
10+
11+
def has_permission(self, source, info, **kwargs) -> bool:
12+
"""Check if the user has dashboard access."""
13+
return (
14+
(user := info.context.request.user)
15+
and user.is_authenticated
16+
and user.github_user.is_owasp_staff
17+
)

backend/apps/owasp/api/internal/queries/project_health_metrics.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from apps.owasp.api.internal.nodes.project_health_metrics import ProjectHealthMetricsNode
88
from apps.owasp.api.internal.nodes.project_health_stats import ProjectHealthStatsNode
99
from apps.owasp.api.internal.ordering.project_health_metrics import ProjectHealthMetricsOrder
10+
from apps.owasp.api.internal.permissions.project_health_metrics import HasDashboardAccess
1011
from apps.owasp.models.project_health_metrics import ProjectHealthMetrics
1112

1213

@@ -19,6 +20,7 @@ class ProjectHealthMetricsQuery:
1920
description="List of project health metrics.",
2021
pagination=True,
2122
ordering=ProjectHealthMetricsOrder,
23+
permission_classes=[HasDashboardAccess],
2224
)
2325
def project_health_metrics(
2426
self,
@@ -39,7 +41,9 @@ def project_health_metrics(
3941
"""
4042
return ProjectHealthMetrics.get_latest_health_metrics()
4143

42-
@strawberry.field
44+
@strawberry.field(
45+
permission_classes=[HasDashboardAccess],
46+
)
4347
def project_health_stats(self) -> ProjectHealthStatsNode:
4448
"""Resolve overall project health stats.
4549
@@ -49,7 +53,9 @@ def project_health_stats(self) -> ProjectHealthStatsNode:
4953
"""
5054
return ProjectHealthMetrics.get_stats()
5155

52-
@strawberry.field
56+
@strawberry.field(
57+
permission_classes=[HasDashboardAccess],
58+
)
5359
def project_health_metrics_distinct_length(
5460
self,
5561
filters: ProjectHealthMetricsFilter | None = None,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"""Permissions for OWASP API views."""
2+
3+
from functools import wraps
4+
5+
from django.http import HttpResponseForbidden
6+
7+
8+
def has_dashboard_permission(request):
9+
"""Check if user has dashboard access."""
10+
return (user := request.user) and user.is_authenticated and user.github_user.is_owasp_staff
11+
12+
13+
def dashboard_access_required(view_func):
14+
"""Require dashboard access permission.
15+
16+
Args:
17+
view_func: The view function to wrap.
18+
19+
Returns:
20+
The wrapped view function that checks for dashboard access permission.
21+
22+
"""
23+
24+
@wraps(view_func)
25+
def _wrapper(request, *args, **kwargs):
26+
if not has_dashboard_permission(request):
27+
return HttpResponseForbidden("You must have dashboard access to access this resource.")
28+
return view_func(request, *args, **kwargs)
29+
30+
return _wrapper

0 commit comments

Comments
 (0)