Skip to content

Commit

Permalink
Merge branch 'main' into regular_ingestion_pacerfree_to_caselaw
Browse files Browse the repository at this point in the history
  • Loading branch information
flooie authored Nov 7, 2024
2 parents 3cf58e5 + ce608bf commit 3186804
Show file tree
Hide file tree
Showing 30 changed files with 745 additions and 162 deletions.
6 changes: 3 additions & 3 deletions cl/alerts/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
)
from cl.alerts.filters import DocketAlertFilter, SearchAlertFilter
from cl.alerts.models import Alert, DocketAlert
from cl.api.api_permissions import IsOwner
from cl.api.api_permissions import IsOwner, V3APIPermission
from cl.api.pagination import MediumAdjustablePagination
from cl.api.utils import LoggingMixin


class SearchAlertViewSet(LoggingMixin, ModelViewSet):
"""A ModelViewset to handle CRUD operations for SearchAlerts."""

permission_classes = [IsOwner, IsAuthenticated]
permission_classes = [IsOwner, IsAuthenticated, V3APIPermission]
serializer_class = SearchAlertSerializer
pagination_class = MediumAdjustablePagination
filterset_class = SearchAlertFilter
Expand All @@ -42,7 +42,7 @@ def get_queryset(self):
class DocketAlertViewSet(LoggingMixin, ModelViewSet):
"""A ModelViewset to handle CRUD operations for DocketAlerts."""

permission_classes = [IsOwner, IsAuthenticated]
permission_classes = [IsOwner, IsAuthenticated, V3APIPermission]
serializer_class = DocketAlertSerializer
pagination_class = MediumAdjustablePagination
filterset_class = DocketAlertFilter
Expand Down
8 changes: 4 additions & 4 deletions cl/alerts/templates/includes/search_alerts/table_header.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<thead>
<tr>
<th>Alert&nbsp;Type</th>
<th>Name and Query</th>
<th>Frequency</th>
<th data-label="Name and Query">Name and Query</th>
<th data-label="Alert&nbsp;Type">Alert&nbsp;Type</th>
<th data-label="Frequency">Frequency</th>
{% if is_profile_dashboard %}
<th colspan="2">Last&nbsp;Hit</th>
<th data-label="Last&nbsp;Hit" colspan="2">Last&nbsp;Hit</th>
{% endif %}
</tr>
</thead>
10 changes: 5 additions & 5 deletions cl/alerts/templates/includes/search_alerts/table_row.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<tr id='alert-row-{{ alert.id }}' {% if hx_swap %} hx-swap-oob="true" {% endif %}>
<td>
<p class="bottom">
<a href="/?{{ alert.query }}" rel="nofollow">{{ alert.name }}</a>
</p>
</td>
<td>
<p class="bottom">
{% if alert.alert_type == SEARCH_TYPES.OPINION %}
Expand All @@ -10,11 +15,6 @@
{% endif %}
</p>
</td>
<td>
<p class="bottom">
<a href="/?{{ alert.query }}" rel="nofollow">{{ alert.name }}</a>
</p>
</td>
{% if is_profile_dashboard %}
<td>{{ alert.date_last_hit|date:"M j, Y"|default:"Never" }}</td>
{% endif %}
Expand Down
89 changes: 89 additions & 0 deletions cl/api/api_permissions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import random

from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User
from django.http import HttpRequest
from rest_framework import permissions
from rest_framework.exceptions import PermissionDenied
from rest_framework.views import APIView

from cl.lib.redis_utils import get_redis_interface


class IsOwner(permissions.BasePermission):
Expand All @@ -8,3 +17,83 @@ def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user


class V3APIPermission(permissions.BasePermission):

r = get_redis_interface("STATS")
v3_blocked_message = (
"As a new user, you don't have permission to access V3 of the API. "
"Please use V4 instead."
)
v3_blocked_list_key = "v3-blocked-users-list"
v3_users_list_key = "v3-users-list"

def is_new_v3_user(self, user: User) -> bool:
"""Check if the user is new for V3 by determining their presence in the
v3-users-list.
:param user: The User to check.
:return: True if the user is new for V3, False otherwise.
"""
is_new_user = self.r.sismember(self.v3_users_list_key, user.id) != 1
return is_new_user

def is_user_v3_blocked(self, user: User) -> bool:
"""Check if the user is already blocked form V3 by determining their
presence in the v3-blocked-users-list.
:param user: The User to check.
:return: True if the user is blocked for V3, False otherwise.
"""
is_blocked_user = (
self.r.sismember(self.v3_blocked_list_key, user.id) == 1
)
return is_blocked_user

@staticmethod
def is_v3_api_request(request: HttpRequest) -> bool:
return getattr(request, "version", None) == "v3"

@staticmethod
def check_request() -> bool:
# Check 1 in 50 requests.
if random.randint(1, 50) == 1:
return True
return False

def has_permission(self, request: HttpRequest, view: APIView) -> bool:
"""Check if the user has permission to access the V3 API.
:param request: The HTTPRequest object.
:param view: The APIview being accessed.
:return: True if the user has permission to access V3, False if not.
"""

if (
not self.is_v3_api_request(request)
or not settings.BLOCK_NEW_V3_USERS # type: ignore
):
# Allow the request if it is not a V3 request
return True

user = request.user
if isinstance(user, AnonymousUser):
# Block V3 for all Anonymous users.
raise PermissionDenied(
"Anonymous users don't have permission to access V3 of the API. "
"Please use V4 instead."
)

if self.is_user_v3_blocked(user):
# Check if user has been blocked from V3.
raise PermissionDenied(self.v3_blocked_message)

if not self.check_request():
return True

if user.is_authenticated and self.is_new_v3_user(user):
# This a new user. Block request and add it to v3-blocked-list
self.r.sadd(self.v3_blocked_list_key, user.id)
raise PermissionDenied(self.v3_blocked_message)
return True
Loading

0 comments on commit 3186804

Please sign in to comment.