feat: Add GET endpoint for Integrity Signature (#27590)
MST-781. Add endpoint to retrieve data related to the integrity signature model.
This commit is contained in:
@@ -117,6 +117,9 @@ urlpatterns = [
|
||||
# Enrollment API RESTful endpoints
|
||||
url(r'^api/enrollment/v1/', include('openedx.core.djangoapps.enrollments.urls')),
|
||||
|
||||
# Agreements API RESTful endpoints
|
||||
url(r'^api/agreements/v1/', include('openedx.core.djangoapps.agreements.urls')),
|
||||
|
||||
# Entitlement API RESTful endpoints
|
||||
url(
|
||||
r'^api/entitlements/',
|
||||
|
||||
163
openedx/core/djangoapps/agreements/tests/test_views.py
Normal file
163
openedx/core/djangoapps/agreements/tests/test_views.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
Tests for agreements views
|
||||
"""
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from django.urls import reverse
|
||||
from edx_toggles.toggles.testutils import override_waffle_flag
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory, AdminFactory
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from ..api import create_integrity_signature
|
||||
from ..toggles import ENABLE_INTEGRITY_SIGNATURE
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=True)
|
||||
class IntegritySignatureViewTests(APITestCase, ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for the Integrity Signature View
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
PASSWORD = "edx"
|
||||
|
||||
OTHER_USERNAME = "Jane"
|
||||
|
||||
STAFF_USERNAME = "Alice"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
self.user = UserFactory.create(
|
||||
username=self.USERNAME,
|
||||
password=self.PASSWORD,
|
||||
)
|
||||
self.other_user = UserFactory.create(
|
||||
username=self.OTHER_USERNAME,
|
||||
password=self.PASSWORD,
|
||||
)
|
||||
|
||||
self.instructor = AdminFactory.create(
|
||||
username=self.STAFF_USERNAME,
|
||||
password=self.PASSWORD,
|
||||
)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
self.course_id = str(self.course.id)
|
||||
|
||||
def _create_signature(self, username, course_id):
|
||||
"""
|
||||
Create integrity signature for a given username and course id
|
||||
"""
|
||||
create_integrity_signature(username, course_id)
|
||||
|
||||
def _assert_response(self, response, expected_response, user=None, course_id=None):
|
||||
"""
|
||||
Assert response is correct for the given information
|
||||
"""
|
||||
assert response.status_code == expected_response
|
||||
if user and course_id:
|
||||
data = response.data
|
||||
assert data['username'] == user.username
|
||||
assert data['course_id'] == course_id
|
||||
|
||||
def test_200_get_for_user_request(self):
|
||||
self._create_signature(self.user.username, self.course_id)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_200_OK, self.user, self.course_id)
|
||||
|
||||
def test_404_get_if_no_signature(self):
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_403_get_if_non_staff(self):
|
||||
self._create_signature(self.other_user.username, self.course_id)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
+ '?username={}'.format(self.other_user.username)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_200_get_for_course_staff_request(self):
|
||||
self._create_signature(self.user.username, self.course_id)
|
||||
|
||||
self.instructor.is_staff = False
|
||||
self.instructor.save()
|
||||
|
||||
CourseStaffRole(self.course.id).add_users(self.instructor)
|
||||
self.client.login(username=self.STAFF_USERNAME, password=self.PASSWORD)
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
+ '?username={}'.format(self.user.username)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_200_OK, self.user, self.course_id)
|
||||
|
||||
def test_403_get_for_other_course_instructor(self):
|
||||
self._create_signature(self.user.username, self.course_id)
|
||||
|
||||
self.instructor.is_staff = False
|
||||
self.instructor.save()
|
||||
|
||||
# create another course and add instructor to that course
|
||||
second_course = CourseFactory.create()
|
||||
CourseStaffRole(second_course.id).add_users(self.instructor)
|
||||
self.client.login(username=self.STAFF_USERNAME, password=self.PASSWORD)
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
+ '?username={}'.format(self.user.username)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def test_200_get_for_admin(self):
|
||||
self._create_signature(self.user.username, self.course_id)
|
||||
|
||||
self.instructor.is_staff = True
|
||||
self.instructor.save()
|
||||
|
||||
self.client.login(username=self.STAFF_USERNAME, password=self.PASSWORD)
|
||||
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
+ '?username={}'.format(self.user.username)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_200_OK, self.user, self.course_id)
|
||||
|
||||
@override_waffle_flag(ENABLE_INTEGRITY_SIGNATURE, active=False)
|
||||
def test_404_for_no_waffle_flag(self):
|
||||
self._create_signature(self.user.username, self.course_id)
|
||||
response = self.client.get(
|
||||
reverse(
|
||||
'integrity_signature',
|
||||
kwargs={'course_id': self.course_id},
|
||||
)
|
||||
)
|
||||
self._assert_response(response, status.HTTP_404_NOT_FOUND)
|
||||
14
openedx/core/djangoapps/agreements/urls.py
Normal file
14
openedx/core/djangoapps/agreements/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
URLs for the Agreements API
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import IntegritySignatureView
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^integrity_signature/{course_id}$'.format(
|
||||
course_id=settings.COURSE_ID_PATTERN
|
||||
), IntegritySignatureView.as_view(), name='integrity_signature'),
|
||||
]
|
||||
96
openedx/core/djangoapps/agreements/views.py
Normal file
96
openedx/core/djangoapps/agreements/views.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Views served by the agreements app. """
|
||||
|
||||
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from common.djangoapps.student import auth
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
|
||||
from .api import get_integrity_signature
|
||||
from .toggles import is_integrity_signature_enabled
|
||||
|
||||
|
||||
def is_user_course_or_global_staff(user, course_id):
|
||||
"""
|
||||
Return whether a user is course staff for a given course, described by the course_id,
|
||||
or is global staff.
|
||||
"""
|
||||
|
||||
return user.is_staff or auth.user_has_role(user, CourseStaffRole(CourseKey.from_string(course_id)))
|
||||
|
||||
|
||||
class AuthenticatedAPIView(APIView):
|
||||
"""
|
||||
Authenticated API View.
|
||||
"""
|
||||
authentication_classes = (SessionAuthentication, JwtAuthentication)
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
|
||||
class IntegritySignatureView(AuthenticatedAPIView):
|
||||
"""
|
||||
Endpoint for an Integrity Signature
|
||||
/integrity_signature/{course_id}
|
||||
|
||||
Supports:
|
||||
HTTP GET: Returns an existing signed integrity agreement (by course id and user)
|
||||
|
||||
HTTP GET
|
||||
** Scenarios **
|
||||
?username=xyz
|
||||
returns an existing signed integrity agreement for the given user and course
|
||||
"""
|
||||
|
||||
def get(self, request, course_id):
|
||||
"""
|
||||
In order to check whether the user has signed the integrity agreement for a given course.
|
||||
|
||||
Should return the following:
|
||||
username (str)
|
||||
course_id (str)
|
||||
created_at (str)
|
||||
|
||||
If a username is not given, it should default to the requesting user (or masqueraded user).
|
||||
Only staff should be able to access this endpoint for other users.
|
||||
"""
|
||||
# check that waffle flag is enabled
|
||||
if not is_integrity_signature_enabled():
|
||||
return Response(
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
# check that user can make request
|
||||
user = request.user.username
|
||||
requested_user = request.GET.get('username')
|
||||
is_staff = is_user_course_or_global_staff(request.user, course_id)
|
||||
|
||||
if not is_staff and requested_user and (user != requested_user):
|
||||
return Response(
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
data={
|
||||
"message": "User does not have permission to view integrity agreement."
|
||||
}
|
||||
)
|
||||
|
||||
username = requested_user if requested_user else user
|
||||
signature = get_integrity_signature(username, course_id)
|
||||
|
||||
if signature is None:
|
||||
return Response(
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
created_at = str(signature.created)
|
||||
|
||||
data = {
|
||||
'username': username,
|
||||
'course_id': course_id,
|
||||
'created_at': created_at,
|
||||
}
|
||||
|
||||
return Response(data)
|
||||
Reference in New Issue
Block a user