Merge pull request #35304 from openedx/michaelroytman/COSMO-421-VerificationAttempt-model
Add VerificationAttempt model to verify_student application.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
0001. Extending Identity Verification
|
||||
#####################################
|
||||
|
||||
Status
|
||||
******
|
||||
|
||||
**Accepted** *2024-08-26*
|
||||
|
||||
Context
|
||||
*******
|
||||
|
||||
The backend implementation of identity verification (IDV) is in the `verify_student Django application`_. The
|
||||
`verify_student Django application`_ also contains a frontend user experience for performing photo IDV via an
|
||||
integration with Software Secure. There is also a `React-based implementation of this flow`_ in the
|
||||
`frontend-app-account MFE`_, so the frontend user experience stored in the `verify_student Django application`_ is often
|
||||
called the "legacy flow".
|
||||
|
||||
The current architecture of the `verify_student Django application`_ requires that any additional implementations of IDV
|
||||
are stored in the application. For example, the Software Secure integration is stored in this application even though
|
||||
it is a custom integration that the Open edX community does not use.
|
||||
|
||||
Different Open edX operators have different IDV needs. There is currently no way to add additional IDV implementations
|
||||
to the platform without committing them to the core. The `verify_student Django application`_ needs enhanced
|
||||
extensibility mechanisms to enable per-deployment integration of IDV implementations without modifying the core.
|
||||
|
||||
Decision
|
||||
********
|
||||
|
||||
* We will support the integration of additional implementations of IDV through the use of Python plugins into the
|
||||
platform.
|
||||
* We will add a ``VerificationAttempt`` model, which will store generic, implementation-agnostic information about an
|
||||
IDV attempt.
|
||||
* We will expose a simple Python API to write and update instances of the ``VerificationAttempt`` model. This will
|
||||
enable plugins to publish information about their IDV attempts to the platform.
|
||||
* The ``VerificationAttempt`` model will be integrated into the `verify_student Django application`_, particularly into
|
||||
the `IDVerificationService`_.
|
||||
* We will emit Open edX events for each status change of a ``VerificationAttempt``.
|
||||
* We will add an Open edX filter hook to change the URL of the photo IDV frontend.
|
||||
|
||||
Consequences
|
||||
************
|
||||
|
||||
* It will become possible for Open edX operators to implement and integrate any additional forms of IDV necessary for
|
||||
their deployment.
|
||||
* The `verify_student Django application`_ will contain both concrete implementations of forms of IDV (i.e. manual, SSO,
|
||||
Software Secure, etc.) and a generic, extensible implementation. The work to deprecate and remove the Software Secure
|
||||
integration and to transition the other existing forms of IDV (i.e. manual and SSO) to Django plugins will occur
|
||||
independently of the improvements to extensibility described in this decision.
|
||||
|
||||
Rejected Alternatives
|
||||
*********************
|
||||
|
||||
We considered introducing a ``fetch_verification_attempts`` filter hook to allow plugins to expose additional
|
||||
``VerificationAttempts`` to the platform in lieu of an additional model. However, doing database queries via filter
|
||||
hooks can cause unpredictable performance problems, and this has been a pain point for Open edX.
|
||||
|
||||
References
|
||||
**********
|
||||
`[Proposal] Add Extensibility Mechanisms to IDV to Enable Integration of New IDV Vendor Persona <https://openedx.atlassian.net/wiki/spaces/OEPM/pages/4307386369/Proposal+Add+Extensibility+Mechanisms+to+IDV+to+Enable+Integration+of+New+IDV+Vendor+Persona>`_
|
||||
`Add Extensibility Mechanisms to IDV to Enable Integration of New IDV Vendor Persona <https://github.com/openedx/platform-roadmap/issues/367>`_
|
||||
|
||||
.. _frontend-app-account MFE: https://github.com/openedx/frontend-app-account
|
||||
.. _IDVerificationService: https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/verify_student/services.py#L55
|
||||
.. _React-based implementation of this flow: https://github.com/openedx/frontend-app-account/tree/master/src/id-verification
|
||||
.. _verify_student Django application: https://github.com/openedx/edx-platform/tree/master/lms/djangoapps/verify_student
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.15 on 2024-08-26 14:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('verify_student', '0014_remove_softwaresecurephotoverification_expiry_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VerificationAttempt',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
|
||||
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
|
||||
('name', models.CharField(blank=True, max_length=255)),
|
||||
('status', models.CharField(choices=[('created', 'created'), ('pending', 'pending'), ('approved', 'approved'), ('denied', 'denied')], max_length=64)),
|
||||
('expiration_datetime', models.DateTimeField(blank=True, null=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -31,6 +31,7 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy
|
||||
from model_utils import Choices
|
||||
from model_utils.models import StatusModel, TimeStampedModel
|
||||
from lms.djangoapps.verify_student.statuses import VerificationAttemptStatus
|
||||
from opaque_keys.edx.django.models import CourseKeyField
|
||||
|
||||
from lms.djangoapps.verify_student.ssencrypt import (
|
||||
@@ -1189,3 +1190,27 @@ class SSPVerificationRetryConfig(ConfigurationModel): # pylint: disable=model-m
|
||||
|
||||
def __str__(self):
|
||||
return str(self.arguments)
|
||||
|
||||
|
||||
class VerificationAttempt(TimeStampedModel):
|
||||
"""
|
||||
The model represents impelementation-agnostic information about identity verification (IDV) attempts.
|
||||
|
||||
Plugins that implement forms of IDV can store information about IDV attempts in this model for use across
|
||||
the platform.
|
||||
"""
|
||||
user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
|
||||
name = models.CharField(blank=True, max_length=255)
|
||||
|
||||
STATUS_CHOICES = [
|
||||
VerificationAttemptStatus.created,
|
||||
VerificationAttemptStatus.pending,
|
||||
VerificationAttemptStatus.approved,
|
||||
VerificationAttemptStatus.denied,
|
||||
]
|
||||
status = models.CharField(max_length=64, choices=[(status, status) for status in STATUS_CHOICES])
|
||||
|
||||
expiration_datetime = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
21
lms/djangoapps/verify_student/statuses.py
Normal file
21
lms/djangoapps/verify_student/statuses.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Status enums for verify_student.
|
||||
"""
|
||||
|
||||
|
||||
class VerificationAttemptStatus:
|
||||
"""This class describes valid statuses for a verification attempt to be in."""
|
||||
|
||||
# This is the initial state of a verification attempt, before a learner has started IDV.
|
||||
created = "created"
|
||||
|
||||
# A verification attempt is pending when it has been started but has not yet been completed.
|
||||
pending = "pending"
|
||||
|
||||
# A verification attempt is approved when it has been approved by some mechanism (e.g. automatic review, manual
|
||||
# review, etc).
|
||||
approved = "approved"
|
||||
|
||||
# A verification attempt is denied when it has been denied by some mechanism (e.g. automatic review, manual review,
|
||||
# etc).
|
||||
denied = "denied"
|
||||
Reference in New Issue
Block a user