From 4ad405e32eacc6f1cbbf01f4b717c73d1db25457 Mon Sep 17 00:00:00 2001 From: adibmbrk Date: Sun, 23 Feb 2025 15:08:44 +0530 Subject: [PATCH 1/8] Initial setup of support token --- contentcuration/contentcuration/models.py | 9 ++++ contentcuration/contentcuration/urls.py | 1 + .../contentcuration/views/admin.py | 18 ++++++++ .../contentcuration/viewsets/channel.py | 41 +++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/contentcuration/contentcuration/models.py b/contentcuration/contentcuration/models.py index 8999c30f53..31f9973afb 100644 --- a/contentcuration/contentcuration/models.py +++ b/contentcuration/contentcuration/models.py @@ -801,6 +801,15 @@ class Channel(models.Model): verbose_name="secret tokens", blank=True, ) + support_token = models.OneToOneField( + SecretToken, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name='support_channels', + verbose_name="support token", + help_text="Token for support access", + ) source_url = models.CharField(max_length=200, blank=True, null=True) demo_server_url = models.CharField(max_length=200, blank=True, null=True) diff --git a/contentcuration/contentcuration/urls.py b/contentcuration/contentcuration/urls.py index 8047ca0bf4..9cdd2a41ba 100644 --- a/contentcuration/contentcuration/urls.py +++ b/contentcuration/contentcuration/urls.py @@ -137,6 +137,7 @@ def get_redirect_url(self, *args, **kwargs): # Add admin endpoints urlpatterns += [ re_path(r'^api/send_custom_email/$', admin_views.send_custom_email, name='send_custom_email'), + re_path(r'^api/support_token_redirect/(?P[^/]+)/$', admin_views.support_token_redirect, name='support_token_redirect'), ] urlpatterns += [re_path(r'^jsreverse/$', django_js_reverse_views.urls_js, name='js_reverse')] diff --git a/contentcuration/contentcuration/views/admin.py b/contentcuration/contentcuration/views/admin.py index 7a55052cbb..883b5223c8 100644 --- a/contentcuration/contentcuration/views/admin.py +++ b/contentcuration/contentcuration/views/admin.py @@ -3,7 +3,9 @@ from django.conf import settings from django.contrib.auth.decorators import login_required from django.core.exceptions import ObjectDoesNotExist +from django.shortcuts import redirect from django.shortcuts import render +from django.shortcuts import get_object_or_404 from rest_framework.authentication import BasicAuthentication from rest_framework.authentication import SessionAuthentication from rest_framework.authentication import TokenAuthentication @@ -17,6 +19,7 @@ from contentcuration.tasks import sendcustomemails_task from contentcuration.utils.messages import get_messages from contentcuration.views.base import current_user_for_context +from contentcuration.models import SecretToken, Channel @is_admin @@ -30,6 +33,21 @@ def send_custom_email(request): return Response({"success": True}) +@is_admin +@api_view(['GET']) +def support_token_redirect(request, token): + try: + support_token = SecretToken.objects.get(token=token) + channel = get_object_or_404(Channel, support_token=support_token) + + # Redirect to the channel edit page + return redirect("channels") + + except SecretToken.DoesNotExist: + return Response({"error": "Invalid token"}, status=404) + + except Channel.DoesNotExist: + return Response({"error": "Channel not found for token"}, status=404) @login_required @browser_is_supported diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index 5cd9b84104..7421586b85 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -777,7 +777,48 @@ def _get_channel_content_languages(self, channel_id, main_tree_id=None) -> List[ logging.error(str(e)) unique_lang_ids = [] return unique_lang_ids + + @action(detail=True, methods=["get"], url_path="support_token", url_name="get-support-token") + def get_support_token(self, request, pk=None): + """ + Retrieve the existing support token for this channel, or null if none exists. + """ + channel = self.get_edit_queryset().get(pk=pk) + token_value = None + if channel.support_token: + token_value = channel.support_token.token + + return Response({"support_token": token_value}, status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"], url_path="support_token", url_name="create-support-token") + def create_support_token(self, request, pk=None): + """ + Create a new support token for this channel. + """ + channel = self.get_edit_queryset().get(pk=pk) + + if channel.support_token is not None: + return Response( + {"error": "Support token already exists for this channel."}, + status=status.HTTP_409_CONFLICT + ) + + channel.support_token = proquint.generate() + channel.save(update_fields=["support_token"]) + Change.create_change( + generate_update_event( + channel.id, + CHANNEL, + {"support_token": secret_token.token}, + channel_id=channel.id, + ), + applied=True, + created_by_id=request.user.id + ) + + data = self.serialize_object(pk=channel.pk) + return Response(data, status=status.HTTP_201_CREATED) @method_decorator( cache_page( From 6c3d371448aaedddd02a6307e3309b7bc419ffae Mon Sep 17 00:00:00 2001 From: adibmbrk Date: Mon, 3 Mar 2025 02:12:06 +0530 Subject: [PATCH 2/8] Add suggested changes and tests --- .../contentcuration/tests/views/test_admin.py | 49 +++++++++++ .../tests/viewsets/test_channel.py | 84 ++++++++++++++++++- .../contentcuration/views/admin.py | 20 +++-- .../contentcuration/viewsets/channel.py | 69 ++++++++------- 4 files changed, 180 insertions(+), 42 deletions(-) create mode 100644 contentcuration/contentcuration/tests/views/test_admin.py diff --git a/contentcuration/contentcuration/tests/views/test_admin.py b/contentcuration/contentcuration/tests/views/test_admin.py new file mode 100644 index 0000000000..d0891677ea --- /dev/null +++ b/contentcuration/contentcuration/tests/views/test_admin.py @@ -0,0 +1,49 @@ +import uuid +from django.urls import reverse +from rest_framework.test import APIClient +from rest_framework import status +from contentcuration.models import Channel, SecretToken +from contentcuration.tests import testdata +from contentcuration.tests.base import StudioAPITestCase + +class SupportTokenRedirectTestCase(StudioAPITestCase): + + @property + def channel_metadata(self): + return { + "name": "Test Channel", + "id": uuid.uuid4().hex, + "description": "A test channel for support token creation.", + } + + def setUp(self): + """ + Set up test data before running each test. + """ + super(SupportTokenRedirectTestCase, self).setUp() + + self.user = testdata.user() + self.user.is_admin = True + self.user.save() + + self.channel = Channel.objects.create(actor_id=self.user.id, **self.channel_metadata) + + self.valid_token_str = "bepud-dibub-dizok" + self.support_token = SecretToken.objects.create(token=self.valid_token_str) + + self.channel.secret_tokens.add(self.support_token) + self.channel.support_token = self.support_token + self.channel.save() + + self.client.force_authenticate(user=self.user) + + def test_valid_token_redirects_to_channel(self): + """ + Test that a valid token redirects to the correct channel page. + """ + url = reverse("support_token_redirect", kwargs={"token": self.valid_token_str}) + response = self.client.get(url, format="json") + + + self.assertEqual(response.status_code, status.HTTP_302_FOUND) # 302 Redirect + self.assertEqual(response.url, f"/channels/{self.channel.id}") diff --git a/contentcuration/contentcuration/tests/viewsets/test_channel.py b/contentcuration/contentcuration/tests/viewsets/test_channel.py index 17549ab128..084b75b5bb 100644 --- a/contentcuration/contentcuration/tests/viewsets/test_channel.py +++ b/contentcuration/contentcuration/tests/viewsets/test_channel.py @@ -12,7 +12,7 @@ from contentcuration import models from contentcuration import models as cc from contentcuration.constants import channel_history -from contentcuration.models import ContentNode +from contentcuration.models import ContentNode, SecretToken from contentcuration.tests import testdata from contentcuration.tests.base import StudioAPITestCase from contentcuration.tests.viewsets.base import generate_create_event @@ -706,3 +706,85 @@ def _perform_action(self, url_path, channel_id): self.client.force_authenticate(user=user) response = self.client.get(reverse(url_path, kwargs={"pk": channel_id}), format="json") return response + +class SupportTokenTestCase(StudioAPITestCase): + + @property + def channel_metadata(self): + return { + "name": "Test Channel", + "id": uuid.uuid4().hex, + "description": "A test channel for support token creation.", + } + + def setUp(self): + super(SupportTokenTestCase, self).setUp() + self.user = testdata.user() + self.channel = models.Channel.objects.create(actor_id=self.user.id, **self.channel_metadata) + self.channel.editors.add(self.user) + self.client.force_authenticate(user=self.user) + self.channel.save() + + def test_create_support_token(self): + """ + Ensure a support token is created successfully for a channel. + """ + url = reverse("channel-support-token", kwargs={"pk": self.channel.id}) + response = self.client.post(url, format="json") + self.assertEqual(response.status_code, 201, response.content) + + self.channel.refresh_from_db() + self.assertIsNotNone(self.channel.support_token) + + # Ensure token exists in SecretToken model + token_exists = models.SecretToken.objects.filter(token=self.channel.support_token.token).exists() + self.assertTrue(token_exists, "Support token was not stored in the SecretToken table.") + + def test_cannot_create_duplicate_support_token(self): + """ + Ensure creating a support token fails if one already exists. + """ + # First request creates the token + url = reverse("channel-support-token", kwargs={"pk": self.channel.id}) + response = self.client.post(url, format="json") + self.assertEqual(response.status_code, 201, response.content) + + # Second request should fail with 409 CONFLICT + response = self.client.post(url, format="json") + self.assertEqual(response.status_code, 409, response.content) + + def test_get_support_token_existing(self): + """ + Ensure the API returns the support token when it exists. + """ + # Create a support token + # token_str = SecretToken.generate_new_token() + support_token = SecretToken.objects.create(token = "test-support-token") + self.channel.support_token = support_token + self.channel.save() + + url = reverse("channel-support-token", kwargs={"pk": self.channel.id}) + response = self.client.get(url, format="json") + print(response.content) + + self.assertEqual(response.status_code, 200, response.content) + self.assertEqual(response.json()["support_token"], "test-support-token") + + def test_get_support_token_channel_not_found(self): + """ + Ensure the API returns 404 when the channel does not exist. + """ + url = reverse("channel-support-token", kwargs={"pk": "non-existent-id"}) + response = self.client.get(url, format="json") + + self.assertEqual(response.status_code, 404, response.content) + + def test_get_support_token_none(self): + """ + Ensure the API returns null when no support token exists. + """ + url = reverse("channel-support-token", kwargs={"pk": self.channel.id}) + response = self.client.get(url, format="json") + + self.assertEqual(response.status_code, 200, response.content) + self.assertIsNone(response.json()["support_token"]) diff --git a/contentcuration/contentcuration/views/admin.py b/contentcuration/contentcuration/views/admin.py index 883b5223c8..9062f9cf50 100644 --- a/contentcuration/contentcuration/views/admin.py +++ b/contentcuration/contentcuration/views/admin.py @@ -1,4 +1,5 @@ import json +import re from django.conf import settings from django.contrib.auth.decorators import login_required @@ -11,6 +12,7 @@ from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import api_view from rest_framework.decorators import authentication_classes +from rest_framework.decorators import permission_classes from rest_framework.response import Response from .json_dump import json_for_parse_from_data @@ -37,17 +39,25 @@ def send_custom_email(request): @api_view(['GET']) def support_token_redirect(request, token): try: - support_token = SecretToken.objects.get(token=token) - channel = get_object_or_404(Channel, support_token=support_token) + # Inline regex for validating Proquint tokens + token_regex = re.compile( + r"^([bdfghjklmnprstvz][aeiou][bdfghjklmnprstvz][aeiou][bdfghjklmnprstvz])" + r"(-[bdfghjklmnprstvz][aeiou][bdfghjklmnprstvz][aeiou][bdfghjklmnprstvz])*$" + ) + + if not token_regex.fullmatch(token): + return Response({"Error": "Invalid token format"}, status=400) + + channel = get_object_or_404(Channel, support_token__token=token) # Redirect to the channel edit page - return redirect("channels") + return redirect(f"channels/{channel.id}") except SecretToken.DoesNotExist: - return Response({"error": "Invalid token"}, status=404) + return Response({"Error": "Invalid token"}, status=404) except Channel.DoesNotExist: - return Response({"error": "Channel not found for token"}, status=404) + return Response({"Error": "Channel not found for token"}, status=404) @login_required @browser_is_supported diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index 7421586b85..691b6747d3 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -32,6 +32,9 @@ from rest_framework.serializers import CharField from rest_framework.serializers import FloatField from rest_framework.serializers import IntegerField +from rest_framework.status import HTTP_200_OK +from rest_framework.status import HTTP_409_CONFLICT +from rest_framework.status import HTTP_404_NOT_FOUND from rest_framework.status import HTTP_201_CREATED from rest_framework.status import HTTP_204_NO_CONTENT from search.models import ChannelFullTextSearch @@ -778,47 +781,41 @@ def _get_channel_content_languages(self, channel_id, main_tree_id=None) -> List[ unique_lang_ids = [] return unique_lang_ids - @action(detail=True, methods=["get"], url_path="support_token", url_name="get-support-token") - def get_support_token(self, request, pk=None): + @action(detail=True, methods=["get", "post"], url_path="support_token", url_name="support-token") + def support_token(self, request, pk=None) -> Union[JsonResponse, HttpResponse, Response]: """ - Retrieve the existing support token for this channel, or null if none exists. - """ - channel = self.get_edit_queryset().get(pk=pk) - token_value = None - if channel.support_token: - token_value = channel.support_token.token - - return Response({"support_token": token_value}, status=status.HTTP_200_OK) - - @action(detail=True, methods=["post"], url_path="support_token", url_name="create-support-token") - def create_support_token(self, request, pk=None): - """ - Create a new support token for this channel. + Handles both: + - GET: Retrieve the existing support token for this channel. + - POST: Create a new support token for this channel. """ + if not self._channel_exists(pk): + return HttpResponseNotFound("No channel matching: {}".format(pk)) + channel = self.get_edit_queryset().get(pk=pk) - - if channel.support_token is not None: - return Response( - {"error": "Support token already exists for this channel."}, - status=status.HTTP_409_CONFLICT - ) - - channel.support_token = proquint.generate() - channel.save(update_fields=["support_token"]) - Change.create_change( - generate_update_event( - channel.id, - CHANNEL, - {"support_token": secret_token.token}, - channel_id=channel.id, - ), - applied=True, - created_by_id=request.user.id + # Handle GET: Retrieve the support token + if request.method == "GET": + token_value = None + if channel.support_token: + token_value = channel.support_token.token + return JsonResponse({"support_token": token_value}) + + # Handle POST: Create a new support token + if request.method == "POST": + if channel.support_token is not None: + return Response( + {"error": "Support token already exists for this channel."}, + status=HTTP_409_CONFLICT ) - - data = self.serialize_object(pk=channel.pk) - return Response(data, status=status.HTTP_201_CREATED) + + token_str = SecretToken.generate_new_token() + support_token = SecretToken.objects.create(token=token_str) + + channel.support_token = support_token + channel.save(update_fields=["support_token"]) + + data = self.serialize_object(pk=channel.pk) + return Response(data, status=HTTP_201_CREATED) @method_decorator( cache_page( From 884d965fc39729aa09bbb7b09587ab48325a09e5 Mon Sep 17 00:00:00 2001 From: adibmbrk Date: Mon, 3 Mar 2025 05:25:45 +0530 Subject: [PATCH 3/8] Remove unused imports --- contentcuration/contentcuration/tests/views/test_admin.py | 1 - contentcuration/contentcuration/views/admin.py | 1 - contentcuration/contentcuration/viewsets/channel.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/contentcuration/contentcuration/tests/views/test_admin.py b/contentcuration/contentcuration/tests/views/test_admin.py index d0891677ea..c92f2abec2 100644 --- a/contentcuration/contentcuration/tests/views/test_admin.py +++ b/contentcuration/contentcuration/tests/views/test_admin.py @@ -1,6 +1,5 @@ import uuid from django.urls import reverse -from rest_framework.test import APIClient from rest_framework import status from contentcuration.models import Channel, SecretToken from contentcuration.tests import testdata diff --git a/contentcuration/contentcuration/views/admin.py b/contentcuration/contentcuration/views/admin.py index 9062f9cf50..71d2c3b5c7 100644 --- a/contentcuration/contentcuration/views/admin.py +++ b/contentcuration/contentcuration/views/admin.py @@ -12,7 +12,6 @@ from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import api_view from rest_framework.decorators import authentication_classes -from rest_framework.decorators import permission_classes from rest_framework.response import Response from .json_dump import json_for_parse_from_data diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index 691b6747d3..f7021665cb 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -32,9 +32,7 @@ from rest_framework.serializers import CharField from rest_framework.serializers import FloatField from rest_framework.serializers import IntegerField -from rest_framework.status import HTTP_200_OK from rest_framework.status import HTTP_409_CONFLICT -from rest_framework.status import HTTP_404_NOT_FOUND from rest_framework.status import HTTP_201_CREATED from rest_framework.status import HTTP_204_NO_CONTENT from search.models import ChannelFullTextSearch From 1adcc8522b0066691ceccba84d8133c3fffcb4bd Mon Sep 17 00:00:00 2001 From: adibmbrk Date: Mon, 3 Mar 2025 21:26:07 +0530 Subject: [PATCH 4/8] Add django migration file --- .../migrations/0152_channel_support_token.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 contentcuration/contentcuration/migrations/0152_channel_support_token.py diff --git a/contentcuration/contentcuration/migrations/0152_channel_support_token.py b/contentcuration/contentcuration/migrations/0152_channel_support_token.py new file mode 100644 index 0000000000..e8141600a8 --- /dev/null +++ b/contentcuration/contentcuration/migrations/0152_channel_support_token.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.24 on 2025-02-23 09:13 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contentcuration', '0151_alter_assessmentitem_type'), + ] + + operations = [ + migrations.AddField( + model_name='channel', + name='support_token', + field=models.OneToOneField(blank=True, help_text='Token for support access', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='support_channels', to='contentcuration.secrettoken', verbose_name='support token'), + ), + ] From 787ada44523f9f7d64f905b0ae6f123c041e27b2 Mon Sep 17 00:00:00 2001 From: adibmbrk Date: Tue, 4 Mar 2025 10:53:20 +0530 Subject: [PATCH 5/8] Fix user incorrectly being logged in to client --- .../contentcuration/tests/views/test_admin.py | 57 ++++++++++++++++--- .../contentcuration/views/admin.py | 16 ++++-- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/contentcuration/contentcuration/tests/views/test_admin.py b/contentcuration/contentcuration/tests/views/test_admin.py index c92f2abec2..eb2ad7a8f9 100644 --- a/contentcuration/contentcuration/tests/views/test_admin.py +++ b/contentcuration/contentcuration/tests/views/test_admin.py @@ -3,9 +3,13 @@ from rest_framework import status from contentcuration.models import Channel, SecretToken from contentcuration.tests import testdata -from contentcuration.tests.base import StudioAPITestCase +from contentcuration.tests.base import StudioTestCase -class SupportTokenRedirectTestCase(StudioAPITestCase): +class SupportTokenRedirectTestCase(StudioTestCase): + + @classmethod + def setUpClass(cls): + super(SupportTokenRedirectTestCase, cls).setUpClass() @property def channel_metadata(self): @@ -19,9 +23,8 @@ def setUp(self): """ Set up test data before running each test. """ - super(SupportTokenRedirectTestCase, self).setUp() - - self.user = testdata.user() + self.setUpBase() + self.user.is_admin = True self.user.save() @@ -29,12 +32,12 @@ def setUp(self): self.valid_token_str = "bepud-dibub-dizok" self.support_token = SecretToken.objects.create(token=self.valid_token_str) - + self.channel.secret_tokens.add(self.support_token) self.channel.support_token = self.support_token self.channel.save() - self.client.force_authenticate(user=self.user) + self.client = self.admin_client() def test_valid_token_redirects_to_channel(self): """ @@ -43,6 +46,42 @@ def test_valid_token_redirects_to_channel(self): url = reverse("support_token_redirect", kwargs={"token": self.valid_token_str}) response = self.client.get(url, format="json") + self.assertEqual(response.status_code, status.HTTP_302_FOUND) + self.assertEqual(response.url, f"channels/{self.channel.id}") + + def test_invalid_token_format(self): + """ + Test that an invalid token format returns 400 Bad Request. + """ + url = reverse("support_token_redirect", kwargs={"token": "invalid_token_123"}) + response = self.client.get(url, format="json") + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("Invalid token format", response.json()["Error"]) + + def test_token_not_found(self): + """ + Test that a non-existent token returns 404 Not Found. + """ + url = reverse("support_token_redirect", kwargs={"token": "bepud-befud-bidup"}) + response = self.client.get(url, format="json") + + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + self.assertIn("Invalid token", response.json()["Error"]) - self.assertEqual(response.status_code, status.HTTP_302_FOUND) # 302 Redirect - self.assertEqual(response.url, f"/channels/{self.channel.id}") + + def test_unauthorized_user_forbidden(self): + """ + Test that a non-admin user gets 403 Forbidden. + """ + self.client.logout() + non_admin_user = testdata.user() + non_admin_user.is_admin = False + non_admin_user.save() + self.client.force_login(non_admin_user) + + url = reverse("support_token_redirect", kwargs={"token": self.valid_token_str}) + response = self.client.get(url, format="json") + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + diff --git a/contentcuration/contentcuration/views/admin.py b/contentcuration/contentcuration/views/admin.py index 71d2c3b5c7..ec090ea924 100644 --- a/contentcuration/contentcuration/views/admin.py +++ b/contentcuration/contentcuration/views/admin.py @@ -12,6 +12,7 @@ from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import api_view from rest_framework.decorators import authentication_classes +from rest_framework.decorators import permission_classes from rest_framework.response import Response from .json_dump import json_for_parse_from_data @@ -21,6 +22,8 @@ from contentcuration.utils.messages import get_messages from contentcuration.views.base import current_user_for_context from contentcuration.models import SecretToken, Channel +from contentcuration.viewsets.user import IsAdminUser +from contentcuration.viewsets.user import IsAuthenticated @is_admin @@ -34,9 +37,13 @@ def send_custom_email(request): return Response({"success": True}) -@is_admin @api_view(['GET']) +@permission_classes([IsAuthenticated, IsAdminUser]) def support_token_redirect(request, token): + + if not request.user.is_admin: + return Response({"Error": "Forbidden"}, status=403) + try: # Inline regex for validating Proquint tokens token_regex = re.compile( @@ -46,15 +53,16 @@ def support_token_redirect(request, token): if not token_regex.fullmatch(token): return Response({"Error": "Invalid token format"}, status=400) + + token_instance = SecretToken.objects.filter(token=token).first() + if not token_instance: + return Response({"Error": "Invalid token"}, status=404) channel = get_object_or_404(Channel, support_token__token=token) # Redirect to the channel edit page return redirect(f"channels/{channel.id}") - except SecretToken.DoesNotExist: - return Response({"Error": "Invalid token"}, status=404) - except Channel.DoesNotExist: return Response({"Error": "Channel not found for token"}, status=404) From d5e63596343e889dc378fa0a9713355cd7e706c4 Mon Sep 17 00:00:00 2001 From: adibmbrk Date: Fri, 7 Mar 2025 15:50:58 +0530 Subject: [PATCH 6/8] Remove unnecessary check --- contentcuration/contentcuration/views/admin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contentcuration/contentcuration/views/admin.py b/contentcuration/contentcuration/views/admin.py index ec090ea924..53683c63fe 100644 --- a/contentcuration/contentcuration/views/admin.py +++ b/contentcuration/contentcuration/views/admin.py @@ -40,10 +40,6 @@ def send_custom_email(request): @api_view(['GET']) @permission_classes([IsAuthenticated, IsAdminUser]) def support_token_redirect(request, token): - - if not request.user.is_admin: - return Response({"Error": "Forbidden"}, status=403) - try: # Inline regex for validating Proquint tokens token_regex = re.compile( From 841cb71f45d50657f819435e5d1358e1cea60814 Mon Sep 17 00:00:00 2001 From: adibmbrk Date: Sat, 8 Mar 2025 23:05:37 +0530 Subject: [PATCH 7/8] Initial channel support token frontend --- .../shared/views/channel/ChannelSharing.vue | 4 + .../views/channel/ChannelSupportToken.vue | 142 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 contentcuration/contentcuration/frontend/shared/views/channel/ChannelSupportToken.vue diff --git a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharing.vue b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharing.vue index 17a608603b..bb7245c21d 100644 --- a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharing.vue +++ b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSharing.vue @@ -4,6 +4,8 @@
+ +

{{ $tr('inviteSubheading') }}

@@ -64,6 +66,7 @@ + + + \ No newline at end of file From fdac2f92aaadd1cf21813b25d348bf06afc42d14 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 8 Mar 2025 17:38:17 +0000 Subject: [PATCH 8/8] [pre-commit.ci lite] apply automatic fixes --- .../frontend/shared/views/channel/ChannelSupportToken.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSupportToken.vue b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSupportToken.vue index 6e2fee7be3..2af95ed969 100644 --- a/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSupportToken.vue +++ b/contentcuration/contentcuration/frontend/shared/views/channel/ChannelSupportToken.vue @@ -7,11 +7,11 @@

{{ $tr('supportTokenDescription') }}

- +
- +
- +