From 356f1875598215819f14c20949d5127b83250be4 Mon Sep 17 00:00:00 2001 From: alangsto <46360176+alangsto@users.noreply.github.com> Date: Thu, 17 Dec 2020 16:13:32 -0500 Subject: [PATCH] added endpoint to retrieve IDV photo urls (#25903) fixed quality issues added comment updated for comments --- lms/djangoapps/verify_student/models.py | 16 ++++ .../verify_student/tests/test_models.py | 12 +++ .../verify_student/tests/test_views.py | 78 ++++++++++++++++++- lms/djangoapps/verify_student/urls.py | 8 ++ lms/djangoapps/verify_student/views.py | 34 ++++++++ 5 files changed, 147 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 4efa57ae73..2d1f8160e3 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -675,6 +675,22 @@ class SoftwareSecurePhotoVerification(PhotoVerification): return init_verification.latest('created_at') if init_verification.exists() else None + @classmethod + def get_verification_from_receipt(cls, receipt_id): + """Get a verification for a user based on the photo receipt_id + + Arguments: + receipt_id(String): receipt ID of the user photo or ID photo + + Return: + SoftwareSecurePhotoVerification (object) or None + """ + try: + verification = cls.objects.get(receipt_id=receipt_id) + return verification + except cls.DoesNotExist: + return None + @status_before_must_be("created") def upload_face_image(self, img_data): """ diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index 3b65b8380d..a6b1484ac4 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -415,6 +415,18 @@ class TestPhotoVerification(TestVerificationBase, MockS3BotoMixin, ModuleStoreTe now() + timedelta(days=10) ) + def test_get_verification_from_receipt(self): + result = SoftwareSecurePhotoVerification.get_verification_from_receipt('') + self.assertIs(result, None) + + user = UserFactory.create() + attempt = SoftwareSecurePhotoVerification(user=user) + attempt.status = PhotoVerification.STATUS.submitted + attempt.save() + receipt_id = attempt.receipt_id + result = SoftwareSecurePhotoVerification.get_verification_from_receipt(receipt_id) + self.assertIsNotNone(result) + class SSOVerificationTest(TestVerificationBase): """ diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index a049f0bf25..a0551ab539 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -38,7 +38,7 @@ from openedx.core.djangoapps.embargo.test_utils import restrict_course from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme from openedx.core.djangoapps.user_api.accounts.api import get_account_settings from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory +from common.djangoapps.student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory from common.djangoapps.util.testing import UrlResetMixin from lms.djangoapps.verify_student.tests import TestVerificationBase from xmodule.modulestore import ModuleStoreEnum @@ -1775,3 +1775,79 @@ class TestReverifyView(TestVerificationBase): response = self.client.get(url) verification_start_url = IDVerificationService.get_verify_location() self.assertRedirects(response, verification_start_url, fetch_redirect_response=False) + + +class TestPhotoURLView(ModuleStoreTestCase, TestVerificationBase): + """ + Tests for the results_callback view. + """ + + def setUp(self): + super(TestPhotoURLView, self).setUp() + + self.user = AdminFactory() + login_success = self.client.login(username=self.user.username, password='test') + self.assertTrue(login_success) + self.attempt = SoftwareSecurePhotoVerification( + status="submitted", + user=self.user + ) + self.attempt.save() + self.receipt_id = self.attempt.receipt_id + + @override_settings( + VERIFY_STUDENT={ + "SOFTWARE_SECURE": { + "API_URL": "https://verify.example.com/submit/", + "API_ACCESS_KEY": "dcf291b5572942f99adaab4c2090c006", + "API_SECRET_KEY": "c392efdcc0354c5f922dc39844ec0dc7", + "FACE_IMAGE_AES_KEY": "f82400259e3b4f88821cd89838758292", + "RSA_PUBLIC_KEY": ( + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDkgtz3fQdiXshy/RfOHkoHlhx/" + "SSPZ+nNyE9JZXtwhlzsXjnu+e9GOuJzgh4kUqo73ePIG5FxVU+mnacvufq2cu1SOx" + "lRYGyBK7qDf9Ym67I5gmmcNhbzdKcluAuDCPmQ4ecKpICQQldrDQ9HWDxwjbbcqpVB" + "PYWkE1KrtypGThmcehLmabf6SPq1CTAGlXsHgUtbWCwV6mqR8yScV0nRLln0djLDm9d" + "L8tIVFFVpAfBaYYh2Cm5EExQZjxyfjWd8P5H+8/l0pmK2jP7Hc0wuXJemIZbsdm+DSD" + "FhCGY3AILGkMwr068dGRxfBtBy/U9U5W+nStvkDdMrSgQezS5+V test@example.com" + ), + "AWS_ACCESS_KEY": "c987c7efe35c403caa821f7328febfa1", + "AWS_SECRET_KEY": "fc595fc657c04437bb23495d8fe64881", + "S3_BUCKET": "test-idv", + "CERT_VERIFICATION_PATH": False, + }, + "DAYS_GOOD_FOR": 10, + }, + AWS_S3_CUSTOM_DOMAIN="test.aws.com" + ) + def test_photo_url_view_returns_data(self): + url = reverse('verification_photo_urls', kwargs={'receipt_id': six.text_type(self.receipt_id)}) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["EdX-ID"], self.receipt_id) + self.assertEqual( + response.data["PhotoID"], + "https://{aws_domain}/photo_id/{receipt_id}".format( + aws_domain=settings.AWS_S3_CUSTOM_DOMAIN, + receipt_id=self.receipt_id + ) + ) + self.assertEqual( + response.data["UserPhoto"], + "https://{aws_domain}/face/{receipt_id}".format( + aws_domain=settings.AWS_S3_CUSTOM_DOMAIN, + receipt_id=self.receipt_id + ) + ) + + def test_photo_url_view_returns_404_if_invalid_receipt_id(self): + url = reverse('verification_photo_urls', kwargs={'receipt_id': six.text_type('00000000-0000-0000-0000-000000000000')}) + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + + def test_403_for_non_staff(self): + self.user = UserFactory() + login_success = self.client.login(username=self.user.username, password='test') + self.assertTrue(login_success) + url = reverse('verification_photo_urls', kwargs={'receipt_id': six.text_type(self.receipt_id)}) + response = self.client.get(url) + self.assertEqual(response.status_code, 403) diff --git a/lms/djangoapps/verify_student/urls.py b/lms/djangoapps/verify_student/urls.py index 165828fcd3..2ad481b0d2 100644 --- a/lms/djangoapps/verify_student/urls.py +++ b/lms/djangoapps/verify_student/urls.py @@ -8,6 +8,8 @@ from django.conf.urls import url from lms.djangoapps.verify_student import views +IDV_RECEIPT_ID_PATTERN = r'(?P[0-9a-z]{8}-([0-9a-z]{4}-){3}[0-9a-z]{12})' + urlpatterns = [ # The user is starting the verification / payment process, # most likely after enrolling in a course and selecting @@ -98,6 +100,12 @@ urlpatterns = [ views.ReverifyView.as_view(), name="verify_student_reverify" ), + + url( + r'^photo-urls/{receipt_id}$'.format(receipt_id=IDV_RECEIPT_ID_PATTERN), + views.PhotoUrlsView.as_view(), + name="verification_photo_urls" + ) ] # Fake response page for incourse reverification ( software secure ) diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index eaaa0f26e5..e1b88610a4 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -49,6 +49,7 @@ from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.track import segment from common.djangoapps.util.db import outer_atomic from common.djangoapps.util.json_request import JsonResponse +from common.djangoapps.util.views import require_global_staff from xmodule.modulestore.django import modulestore from .services import IDVerificationService @@ -1213,3 +1214,36 @@ class ReverifyView(View): """ IDV_workflow = IDVerificationService.get_verify_location() return redirect(IDV_workflow) + + +class PhotoUrlsView(APIView): + """ + This can be used to help debug IDV photos + """ + + @method_decorator(require_global_staff) + def get(self, request, receipt_id): + """ + Endpoint for retrieving photo urls for IDV + GET /verify_student/photo_urls/{receipt_id} + + Returns: + 200 OK + { + "EdX-ID": receipt_id, + "ExpectedName": user profile name, + "PhotoID": id photo S3 url, + "PhotoIDKey": encrypted photo id key, + "UserPhoto": face photo S3 url, + "UserPhotoKey": encrypted user photo key, + } + """ + verification = SoftwareSecurePhotoVerification.get_verification_from_receipt(receipt_id) + if verification: + _, body = verification.create_request() + # remove this key, as it isn't needed + body.pop('SendResponseTo') + return Response(body) + + log.warning(u"Could not find verification with receipt ID %s.", receipt_id) + raise Http404