added two endpoints for IDV decryption on stage (#25939)
simplified public keys made migration fixes for quality added pylint fixes fixed for pylint added endpoint to retrieve user's receipt_ids added tests for 404 with decryption error fixed for quality fixed for quality updates for feedback removed unnecessary method fixed quality issue updated tests
This commit is contained in:
@@ -37,14 +37,20 @@ from opaque_keys.edx.django.models import CourseKeyField
|
||||
|
||||
from lms.djangoapps.verify_student.ssencrypt import (
|
||||
encrypt_and_encode,
|
||||
decode_and_decrypt,
|
||||
generate_signed_message,
|
||||
random_aes_key,
|
||||
rsa_encrypt
|
||||
rsa_encrypt,
|
||||
rsa_decrypt
|
||||
)
|
||||
from openedx.core.djangoapps.signals.signals import LEARNER_NOW_VERIFIED
|
||||
from openedx.core.storage import get_storage
|
||||
|
||||
from .utils import auto_verify_for_testing_enabled, earliest_allowed_verification_date, submit_request_to_ss
|
||||
from .utils import (
|
||||
auto_verify_for_testing_enabled,
|
||||
earliest_allowed_verification_date,
|
||||
submit_request_to_ss
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -691,6 +697,14 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
except cls.DoesNotExist:
|
||||
return None
|
||||
|
||||
def _save_image_to_storage(self, path, img_data):
|
||||
"""
|
||||
Given a path and data, save to S3
|
||||
Separated out for ease of mocking in testing
|
||||
"""
|
||||
buff = ContentFile(img_data)
|
||||
self._storage.save(path, buff)
|
||||
|
||||
@status_before_must_be("created")
|
||||
def upload_face_image(self, img_data):
|
||||
"""
|
||||
@@ -716,9 +730,10 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
else:
|
||||
aes_key = aes_key_str.decode("hex")
|
||||
|
||||
encrypted_data = encrypt_and_encode(img_data, aes_key)
|
||||
|
||||
path = self._get_path("face")
|
||||
buff = ContentFile(encrypt_and_encode(img_data, aes_key))
|
||||
self._storage.save(path, buff)
|
||||
self._save_image_to_storage(path, encrypted_data)
|
||||
|
||||
@status_before_must_be("created")
|
||||
def upload_photo_id_image(self, img_data):
|
||||
@@ -748,8 +763,8 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
|
||||
# Save this to the storage backend
|
||||
path = self._get_path("photo_id")
|
||||
buff = ContentFile(encrypt_and_encode(img_data, aes_key))
|
||||
self._storage.save(path, buff)
|
||||
encrypted_data = encrypt_and_encode(img_data, aes_key)
|
||||
self._save_image_to_storage(path, encrypted_data)
|
||||
|
||||
# Update our record fields
|
||||
if six.PY3:
|
||||
@@ -759,6 +774,61 @@ class SoftwareSecurePhotoVerification(PhotoVerification):
|
||||
|
||||
self.save()
|
||||
|
||||
def _get_image_from_storage(self, path):
|
||||
"""
|
||||
Given a path, read data from storage and return
|
||||
Separated for ease of mocking in testing
|
||||
"""
|
||||
with self._storage.open(path, mode='rb') as img_file:
|
||||
byte_img_data = img_file.read()
|
||||
return byte_img_data
|
||||
|
||||
@status_before_must_be("must_retry", "submitted", "approved", "denied")
|
||||
def download_face_image(self):
|
||||
"""
|
||||
Download the associated face image from storage
|
||||
"""
|
||||
if not settings.VERIFY_STUDENT["SOFTWARE_SECURE"].get("RSA_PRIVATE_KEY", None):
|
||||
return None
|
||||
path = self._get_path("face")
|
||||
byte_img_data = self._get_image_from_storage(path)
|
||||
|
||||
aes_key_str = settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["FACE_IMAGE_AES_KEY"]
|
||||
|
||||
try:
|
||||
if six.PY3:
|
||||
aes_key = codecs.decode(aes_key_str, "hex")
|
||||
else:
|
||||
aes_key = aes_key_str.decode("hex")
|
||||
|
||||
img_bytes = decode_and_decrypt(byte_img_data, aes_key)
|
||||
return img_bytes
|
||||
except: # pylint: disable=bare-except
|
||||
return None
|
||||
|
||||
@status_before_must_be("must_retry", "submitted", "approved", "denied")
|
||||
def download_photo_id_image(self):
|
||||
"""
|
||||
Download the associated id image from storage
|
||||
"""
|
||||
if not settings.VERIFY_STUDENT["SOFTWARE_SECURE"].get("RSA_PRIVATE_KEY", None):
|
||||
return None
|
||||
|
||||
path = self._get_path("photo_id")
|
||||
byte_img_data = self._get_image_from_storage(path)
|
||||
|
||||
try:
|
||||
# decode rsa encrypted aes key from base64
|
||||
rsa_encrypted_aes_key = base64.urlsafe_b64decode(self.photo_id_key)
|
||||
|
||||
# decrypt aes key using rsa private key
|
||||
rsa_private_key_str = settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["RSA_PRIVATE_KEY"]
|
||||
decrypted_aes_key = rsa_decrypt(rsa_encrypted_aes_key, rsa_private_key_str)
|
||||
img_bytes = decode_and_decrypt(byte_img_data, decrypted_aes_key)
|
||||
return img_bytes
|
||||
except: # pylint: disable=bare-except
|
||||
return None
|
||||
|
||||
@status_before_must_be("must_retry", "ready", "submitted")
|
||||
def submit(self, copy_id_photo_from=None):
|
||||
"""
|
||||
|
||||
@@ -6,6 +6,8 @@ Tests of verify_student views.
|
||||
from datetime import timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import ddt
|
||||
import httpretty
|
||||
import mock
|
||||
@@ -34,6 +36,7 @@ from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, VerificationDeadline
|
||||
from lms.djangoapps.verify_student.services import IDVerificationService
|
||||
from lms.djangoapps.verify_student.views import PayAndVerifyView, checkout_with_ecommerce_service, render_to_response
|
||||
from lms.djangoapps.verify_student.ssencrypt import encrypt_and_encode, rsa_encrypt
|
||||
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
|
||||
@@ -55,6 +58,43 @@ render_mock = Mock(side_effect=mock_render_to_response)
|
||||
|
||||
PAYMENT_DATA_KEYS = {'payment_processor_name', 'payment_page_url', 'payment_form_data'}
|
||||
|
||||
RSA_PUBLIC_KEY = b"""-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1hLVjP0oV0Uy/+jQ+Upz
|
||||
c+eYc4Pyflb/WpfgYATggkoQdnsdplmvPtQr85+utgqKPxOh+PvYGW8QNUzjLIu4
|
||||
5/GlmvBa82i1jRMgEAxGI95bz7j9DtH+7mnj+06zR5xHwT49jK0zMs5MjMaz5WRq
|
||||
BUNkz7dxWzDrYJZQx230sPp6upy1Y5H5O8SnJVdghsh8sNciS4Bo4ZONQ3giBwxz
|
||||
h5svjspz1MIsOoShjbAdfG+4VX7sVwYlw2rnQeRsMH5/xpnNeqtScyOMoz0N9UDG
|
||||
dtRMNGa2MihAg7zh7/zckbUrtf+o5wQtlCJL1Kdj4EjshqYvCxzWnSM+MaYAjb3M
|
||||
EQIDAQAB
|
||||
-----END PUBLIC KEY-----"""
|
||||
RSA_PRIVATE_KEY = b"""-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA1hLVjP0oV0Uy/+jQ+Upzc+eYc4Pyflb/WpfgYATggkoQdnsd
|
||||
plmvPtQr85+utgqKPxOh+PvYGW8QNUzjLIu45/GlmvBa82i1jRMgEAxGI95bz7j9
|
||||
DtH+7mnj+06zR5xHwT49jK0zMs5MjMaz5WRqBUNkz7dxWzDrYJZQx230sPp6upy1
|
||||
Y5H5O8SnJVdghsh8sNciS4Bo4ZONQ3giBwxzh5svjspz1MIsOoShjbAdfG+4VX7s
|
||||
VwYlw2rnQeRsMH5/xpnNeqtScyOMoz0N9UDGdtRMNGa2MihAg7zh7/zckbUrtf+o
|
||||
5wQtlCJL1Kdj4EjshqYvCxzWnSM+MaYAjb3MEQIDAQABAoIBAQCviuA87fdfoOoS
|
||||
OerrEacc20QDLaby/QoGUtZ2RmmHzY40af7FQ3PWFIw6Ca5trrTwxnuivXnWWWG0
|
||||
I2mCRM0Kvfgr1n7ubOW7WnyHTFlT3mnxK2Ov/HmNLZ36nO2cgkXA6/Xy3rBGMC9L
|
||||
nUE1kSLzT/Fh965ntfS9zmVNNBhb6no0rVkGx5nK3vTI6kUmaa0m+E7KL/HweO4c
|
||||
JodhN8CX4gpxSrkuwJ7IHEPYspqc0jInMYKLmD3d2g3BiOctjzFmaj3lV5AUlujW
|
||||
z7/LVe5WAEaaxjwaMvwqrJLv9ogxWU3etJf22+Yy7r5gbPtqpqJrCZ5+WpGnUHws
|
||||
3mMGP2QBAoGBAOc3pzLFgGUREVPSFQlJ06QFtfKYqg9fFHJCgWu/2B2aVZc2aO/t
|
||||
Zhuoz+AgOdzsw+CWv7K0FH9sUkffk2VKPzwwwufLK3avD9gI0bhmBAYvdhS6A3nO
|
||||
YM3W+lvmaJtFL00K6kdd+CzgRnBS9cZ70WbcbtqjdXI6+mV1WdGUTLhBAoGBAO0E
|
||||
xhD4z+GjubSgfHYEZPgRJPqyUIfDH+5UmFGpr6zlvNN/depaGxsbhW8t/V6xkxsG
|
||||
MCgic7GLMihEiUMx1+/snVs5bBUx7OT9API0d+vStHCFlTTe6aTdmiduFD4PbDsq
|
||||
6E4DElVRqZhpIYusdDh7Z3fO2hm5ad4FfMlx65/RAoGAPYEfV7ETs06z9kEG2X6q
|
||||
7pGaUZrsecRH8xDfzmKswUshg2S0y0WyCJ+CFFNeMPdGL4LKIWYnobGVvYqqcaIr
|
||||
af5qijAQMrTkmQnXh56TaXXMijzk2czdEUQjOrjykIL5zxudMDi94GoUMqLOv+qF
|
||||
zD/MuRoMDsPDgaOSrd4t/kECgYEAzwBNT8NOIz3P0Z4cNSJPYIvwpPaY+IkE2SyO
|
||||
vzuYj0Mx7/Ew9ZTueXVGyzv6PfqOhJqZ8mNscZIlIyAAVWwxsHwRTfvPlo882xzP
|
||||
97i1R4OFTYSNNFi+69sSZ/9utGjZ2K73pjJuj487tD2VK5xZAH9edTd2KeNSP7LB
|
||||
MlpJNBECgYAmIswPdldm+G8SJd5j9O2fcDVTURjKAoSXCv2j4gEZzzfudpLWNHYu
|
||||
l8N6+LEIVTMAytPk+/bImHvGHKZkCz5rEMSuYJWOmqKI92rUtI6fz5DUb3XSbrwT
|
||||
3W+sdGFUK3GH1NAX71VxbAlFVLUetcMwai1+wXmGkRw6A7YezVFnhw==
|
||||
-----END RSA PRIVATE KEY-----"""
|
||||
|
||||
|
||||
def _mock_payment_processors():
|
||||
"""
|
||||
@@ -1259,14 +1299,7 @@ class TestSubmitPhotosForVerification(MockS3BotoMixin, TestVerificationBase):
|
||||
"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"
|
||||
),
|
||||
"RSA_PUBLIC_KEY": RSA_PUBLIC_KEY,
|
||||
"AWS_ACCESS_KEY": "c987c7efe35c403caa821f7328febfa1",
|
||||
"AWS_SECRET_KEY": "fc595fc657c04437bb23495d8fe64881",
|
||||
"S3_BUCKET": "test.example.com",
|
||||
@@ -1784,14 +1817,7 @@ class TestReverifyView(TestVerificationBase):
|
||||
"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"
|
||||
),
|
||||
"RSA_PUBLIC_KEY": RSA_PUBLIC_KEY,
|
||||
"AWS_ACCESS_KEY": "c987c7efe35c403caa821f7328febfa1",
|
||||
"AWS_SECRET_KEY": "fc595fc657c04437bb23495d8fe64881",
|
||||
"CERT_VERIFICATION_PATH": False,
|
||||
@@ -1803,9 +1829,9 @@ class TestReverifyView(TestVerificationBase):
|
||||
},
|
||||
},
|
||||
)
|
||||
class TestPhotoURLView(ModuleStoreTestCase, TestVerificationBase):
|
||||
class TestPhotoURLView(TestVerificationBase):
|
||||
"""
|
||||
Tests for the results_callback view.
|
||||
Tests for the photo url view.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -1853,3 +1879,155 @@ class TestPhotoURLView(ModuleStoreTestCase, TestVerificationBase):
|
||||
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)
|
||||
|
||||
|
||||
@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": b'32fe72aaf2abb44de9e161131b5435c8d37cbdb6f5df242ae860b283115f2dae',
|
||||
"RSA_PUBLIC_KEY": RSA_PUBLIC_KEY,
|
||||
"RSA_PRIVATE_KEY": RSA_PRIVATE_KEY,
|
||||
"AWS_ACCESS_KEY": "c987c7efe35c403caa821f7328febfa1",
|
||||
"AWS_SECRET_KEY": "fc595fc657c04437bb23495d8fe64881",
|
||||
"S3_BUCKET": "test-idv",
|
||||
"CERT_VERIFICATION_PATH": False,
|
||||
},
|
||||
"DAYS_GOOD_FOR": 10,
|
||||
"STORAGE_CLASS": 'storages.backends.s3boto.S3BotoStorage',
|
||||
"STORAGE_KWARGS": {
|
||||
'bucket': 'test-idv',
|
||||
},
|
||||
}
|
||||
)
|
||||
@ddt.ddt
|
||||
class TestDecodeImageViews(MockS3BotoMixin, TestVerificationBase):
|
||||
"""
|
||||
Test for both face and photo id image decoding views
|
||||
"""
|
||||
|
||||
IMAGE_DATA = "abcd,1234"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = AdminFactory()
|
||||
login_success = self.client.login(username=self.user.username, password='test')
|
||||
self.assertTrue(login_success)
|
||||
|
||||
def _mock_submit_images(self):
|
||||
"""
|
||||
Mocks submitting images for IDV and saving to S3
|
||||
"""
|
||||
# create an attempt with a submitted status, and create a photo_id_key to use
|
||||
# for decryption
|
||||
attempt = SoftwareSecurePhotoVerification(
|
||||
status="submitted",
|
||||
user=self.user
|
||||
)
|
||||
rsa_key_str = settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["RSA_PUBLIC_KEY"]
|
||||
rsa_encrypted_aes_key = rsa_encrypt(
|
||||
codecs.decode(
|
||||
settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["FACE_IMAGE_AES_KEY"],
|
||||
"hex"
|
||||
),
|
||||
rsa_key_str
|
||||
)
|
||||
attempt.photo_id_key = codecs.encode(rsa_encrypted_aes_key, 'base64').decode('utf-8')
|
||||
|
||||
attempt.save()
|
||||
|
||||
def _decode_image(self, receipt_id, img_type):
|
||||
"""
|
||||
Test function used to call decoding endpoint
|
||||
Arg:
|
||||
receipt_id(str): receipt ID for endpoint url
|
||||
img_type(str): 'face' or 'photo_id', used to determine which endpoint to use
|
||||
"""
|
||||
url_name = 'verification_decrypt_face_image'
|
||||
if img_type == 'photo_id':
|
||||
url_name = 'verification_decrypt_photo_id_image'
|
||||
url = reverse(url_name, kwargs={'receipt_id': six.text_type(receipt_id)})
|
||||
|
||||
response = self.client.get(url)
|
||||
|
||||
return response
|
||||
|
||||
@ddt.data("face", "photo_id")
|
||||
@patch.object(SoftwareSecurePhotoVerification, '_get_image_from_storage')
|
||||
def test_download_image_response(self, img_type, _mock_get_storage):
|
||||
_mock_get_storage.return_value = encrypt_and_encode(
|
||||
b'\xd7m\xf8',
|
||||
codecs.decode(settings.VERIFY_STUDENT["SOFTWARE_SECURE"]["FACE_IMAGE_AES_KEY"], "hex")
|
||||
)
|
||||
# upload 'images'
|
||||
self._mock_submit_images()
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user)
|
||||
receipt_id = attempt.receipt_id
|
||||
|
||||
#mock downloading and decrypting images
|
||||
response = self._decode_image(receipt_id, img_type)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, base64.b64decode(self.IMAGE_DATA.split(",")[1]))
|
||||
|
||||
@ddt.data("face", "photo_id")
|
||||
def test_403_for_non_staff(self, img_type):
|
||||
self.user = UserFactory()
|
||||
login_success = self.client.login(username=self.user.username, password='test')
|
||||
self.assertTrue(login_success)
|
||||
|
||||
self._mock_submit_images()
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user)
|
||||
receipt_id = attempt.receipt_id
|
||||
|
||||
# mock downloading and decrypting images
|
||||
response = self._decode_image(receipt_id, img_type)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
@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": b'32fe72aaf2abb44de9e161131b5435c8d37cbdb6f5df242ae860b283115f2dae',
|
||||
"RSA_PUBLIC_KEY": RSA_PUBLIC_KEY,
|
||||
"AWS_ACCESS_KEY": "c987c7efe35c403caa821f7328febfa1",
|
||||
"AWS_SECRET_KEY": "fc595fc657c04437bb23495d8fe64881",
|
||||
"S3_BUCKET": "test-idv",
|
||||
"CERT_VERIFICATION_PATH": False,
|
||||
},
|
||||
"DAYS_GOOD_FOR": 10,
|
||||
}
|
||||
)
|
||||
@ddt.data("face", "photo_id")
|
||||
def test_403_for_non_staging(self, img_type):
|
||||
self._mock_submit_images()
|
||||
attempt = SoftwareSecurePhotoVerification.objects.get(user=self.user)
|
||||
receipt_id = attempt.receipt_id
|
||||
|
||||
# mock downloading and decrypting images
|
||||
response = self._decode_image(receipt_id, img_type)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
@ddt.data("face", "photo_id")
|
||||
def test_404_if_invalid_receipt_id(self, img_type):
|
||||
response = self._decode_image('00000000-0000-0000-0000-000000000000', img_type)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@ddt.data("face", "photo_id")
|
||||
@patch.object(SoftwareSecurePhotoVerification, '_get_image_from_storage')
|
||||
def test_404_for_decryption_error(self, img_type, _mock_get_storage):
|
||||
_mock_get_storage.return_value = None
|
||||
# create verification with no img data
|
||||
attempt = SoftwareSecurePhotoVerification(
|
||||
status="submitted",
|
||||
user=self.user
|
||||
)
|
||||
attempt.save()
|
||||
receipt_id = attempt.receipt_id
|
||||
|
||||
# mock downloading and decrypting images
|
||||
response = self._decode_image(receipt_id, img_type)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@@ -106,6 +106,18 @@ urlpatterns = [
|
||||
views.PhotoUrlsView.as_view(),
|
||||
name="verification_photo_urls"
|
||||
),
|
||||
|
||||
url(
|
||||
r'^decrypt-idv-images/face/{receipt_id}$'.format(receipt_id=IDV_RECEIPT_ID_PATTERN),
|
||||
views.DecryptFaceImageView.as_view(),
|
||||
name="verification_decrypt_face_image"
|
||||
),
|
||||
|
||||
url(
|
||||
r'^decrypt-idv-images/photo-id/{receipt_id}$'.format(receipt_id=IDV_RECEIPT_ID_PATTERN),
|
||||
views.DecryptPhotoIDImageView.as_view(),
|
||||
name="verification_decrypt_photo_id_image"
|
||||
),
|
||||
]
|
||||
|
||||
# Fake response page for incourse reverification ( software secure )
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
from django.db import transaction
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -24,7 +24,6 @@ from django.views.decorators.http import require_POST
|
||||
from django.views.generic.base import View
|
||||
from edx_rest_api_client.exceptions import SlumberBaseException
|
||||
from ipware.ip import get_ip
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
@@ -1225,7 +1224,7 @@ class PhotoUrlsView(APIView):
|
||||
def get(self, request, receipt_id):
|
||||
"""
|
||||
Endpoint for retrieving photo urls for IDV
|
||||
GET /verify_student/photo_urls/{receipt_id}
|
||||
GET /verify_student/photo-urls/{receipt_id}
|
||||
|
||||
Returns:
|
||||
200 OK
|
||||
@@ -1247,3 +1246,67 @@ class PhotoUrlsView(APIView):
|
||||
|
||||
log.warning(u"Could not find verification with receipt ID %s.", receipt_id)
|
||||
raise Http404
|
||||
|
||||
|
||||
class DecryptFaceImageView(APIView):
|
||||
"""
|
||||
Endpoint to retrieve decrypted IDV face image data. Can only be used on stage.
|
||||
"""
|
||||
|
||||
@method_decorator(require_global_staff)
|
||||
def get(self, request, receipt_id):
|
||||
"""
|
||||
Endpoint used for decrypting images on stage based on a given receipt ID
|
||||
GET /verify_student/decrypt-idv-images/face/{receipt_id}
|
||||
|
||||
Returns:
|
||||
200 OK
|
||||
{
|
||||
img
|
||||
}
|
||||
"""
|
||||
# if this endpoint is not being accessed on stage, raise a 403. Only stage will have an RSA_PRIVATE_KEY
|
||||
if not settings.VERIFY_STUDENT["SOFTWARE_SECURE"].get("RSA_PRIVATE_KEY", None):
|
||||
log.warning(u"Cannot access image decryption outside of staging environment")
|
||||
return HttpResponseForbidden()
|
||||
|
||||
verification = SoftwareSecurePhotoVerification.get_verification_from_receipt(receipt_id)
|
||||
if verification:
|
||||
user_photo = verification.download_face_image()
|
||||
if user_photo:
|
||||
return HttpResponse(user_photo, content_type="image/png")
|
||||
|
||||
log.warning(u"Could not decrypt face image for receipt ID %s.", receipt_id)
|
||||
raise Http404
|
||||
|
||||
|
||||
class DecryptPhotoIDImageView(APIView):
|
||||
"""
|
||||
Endpoint to retrieve decrypted IDV photo ID image data. Can only be used on stage.
|
||||
"""
|
||||
|
||||
@method_decorator(require_global_staff)
|
||||
def get(self, request, receipt_id):
|
||||
"""
|
||||
Endpoint used for decrypting images on stage based on a given receipt ID
|
||||
GET /verify_student/decrypt-idv-images/photo-id/{receipt_id}
|
||||
|
||||
Returns:
|
||||
200 OK
|
||||
{
|
||||
img
|
||||
}
|
||||
"""
|
||||
# if this endpoint is not being accessed on stage, raise a 403. Only stage will have an RSA_PRIVATE_KEY
|
||||
if not settings.VERIFY_STUDENT["SOFTWARE_SECURE"].get("RSA_PRIVATE_KEY", None):
|
||||
log.warning(u"Cannot access image decryption outside of staging environment")
|
||||
return HttpResponseForbidden()
|
||||
|
||||
verification = SoftwareSecurePhotoVerification.get_verification_from_receipt(receipt_id)
|
||||
if verification:
|
||||
id_photo = verification.download_photo_id_image()
|
||||
if id_photo:
|
||||
return HttpResponse(id_photo, content_type="image/png")
|
||||
|
||||
log.warning(u"Could not decrypt photo ID image for receipt ID %s.", receipt_id)
|
||||
raise Http404
|
||||
|
||||
Reference in New Issue
Block a user