...
Backend Extensibility Mechanisms
...
Generic Model
In order for the platform to continue to function with respect to the edx-platform verify_student Django application .
Filter Hooks
We propose integrating filter hooks into the IDVerificationService. The IDVerificationService serves as an interface to the verify_student Django application and is used throughout the platform. Callers of this service primarily use it to read data from the IDV related models and to get the URL to the IDV flow.
We propose the following two filters, which will enable full integration of the IDVerificationService with additional IDV implementations.
IDVerificationAttemptsRequested(verifications, uuids, users, statuseswhile also enabling integration with other implementations of IDV, the platform needs a way to store generic, implementation-agnostic information about IDV attempts.
We will introduce a new model VerificationAttempt
to store this data. This model will hook into the IDVerificationService to expose these attempts across the platform.
The model will likely contain the following fields.
created
modified
user
name
status
expiration_datetime
The status
field will have four options - created
, pending
, approved
, and denied
. These statuses correspond to implementation-agnostic statuses that are sensible for use in the core.
Hooks
We propose using the hooks extension framework to integrate additional IDV implementations into the edx-platform verify_student Django application.
Filter Hooks
We propose integrating a filter hook into the IDVerificationService. The IDVerificationService serves as an interface to the verify_student Django application and is used throughout the platform. Callers of this service primarily use it to read data from the IDV related models and to get the URL to the IDV flow.
We propose the following filter, which will enable full integration of the IDVerificationService with additional IDV implementations.
url = IDVerificationURLRequested(url)
This filter hook will be used to read IDV records from pluginsfetch the URL to the IDV flow. This will allow Open edX operators to expose supplemental IDV records override the URL to the platform via the IDVerificationService.
The uuids
, users
, and statuses
parameters are intended to be iterables of values to filter the IDV records by, corresponding to the uuid
, user.id
and status
fields of the IDVerificationAttempt model, which is the abstract base class that SoftwareSecurePhotoVerification (via PhotoVerification), SSOVerification, and ManualVerification inherit from. As a note uuid
is not currently a field on the IDVerificationAttempt model; our proposal for its addition is discussed in Refactoring.
verifications
in intended to be an iterable of data class instances that represent IDV records. We propose replacing the use of model instances in the IDVerificationService with data classes. Using data classes will avoid exposing data models outside the verify_student Django application, as encouraged by OEP-49: Django App Patterns.
An app’s Django models and other internal data structures should not be exposed via its Python APIs (unless performance requires it).
Using data classes will also simplify the integration of additional IDV implementations by decoupling the verify_student data model from the data model of additional IDV implementations. Because plugins cannot and likely should not import the IDVerificationAttempt model to use as a parent class, if we continue to use model instances, any additional IDV models must replicate the database schema used by the IDVerificationAttempt model.
The IDVerificationService uses some additional database level filters (e.g. created_at__gt) that would not be supported by this filter. To avoid needing to support additional filter parameters, we propose only supporting querying by a select few fields and doing any additional querying in Python code. This will require some additional lightweight refactoring.
IDVerificationURLRequested(url)
This filter hook will be used to fetch the URL to the IDV flow. This will allow operators to override the URL to the IDV flow.
Event Hooks
We propose introducing event hooks related to the IDV attempt lifecycle. Taking the EXAM_ATTEMPT_*
Open edX events as a model, we propose introducing one Open edX event per IDV status.
IDV_ATTEMPT_CREATED
IDV_ATTEMPT_READY
IDV_ATTEMPT_SUBMITTED
IDV_ATTEMPT_MUST_RETRY
IDV_ATTEMPT_APPROVED
IDV_ATTEMPT_DENIED
It’s worth noting that the statuses of the IDVerificationAttempt model may not be entirely generic. The statuses are somewhat specific to Software Secure. As an example, they make the assumption that IDV is an asynchronous process (e.g. the distinction between ready
and submitted
). However, Persona, for example, performs IDV verifications within 5 seconds, so there is no valuable distinction between a ready
and submitted
state. These may not be the best statuses to use, but they map to the existing database states.
We could limit the set of events to more widely applicable ones.
IDV_ATTEMPT_CREATED
IDV_ATTEMPT_SUBMITTED
IDV_ATTEMPT_APPROVED
IDV_ATTEMPT_DENIED
The actual shape of the event data could be determined later.
Frontend Extensibility Mechanisms
We propose using the frontend-plugin-framework to integrate additional IDV implementations into the frontend-app-account MFE.
The current IDV flow is hosted in the frontend-app-account MFE. Although it works well, it is incompatible with Persona’s IDV flow. The Persona application collects all PII, government ID photos, and selfie photos (i.e. portrait photos) directly in the application, but edX’s current IDV flow performs all of those functions and submits the collected photos to Software Secure via the LMS.
For this reason, we need to replace the current IDV flow. In order to do this without modifying the current IDV flow hosted in the frontend-app-account MFE, we propose the addition of a PluginSlot from the frontend-plugin-framework to the frontend-app-account MFE. This PluginSlot would wrap the use of the IdVerificationPage in the id-verification
Route
. This would allow us to replace the entire IDV component with a new Persona integration component without needing to modify the route or the router.
There are a few other options for where to add a PluginSlot. These are documented in Alternative PluginSlot Locations.
Sample code is shown below for illustrative purposes.
Code Block |
---|
const IdVerificationPageSlot = () => (
<PluginSlot
id="id_verification_slot"
pluginProps={{
courseId,
}}
/>
<IdVerificationPage />
</PluginSlot>
)
...
subscribe(APP_READY, () => {
ReactDOM.render(
...
<Route path="/notifications/:courseId" element={<NotificationPreferences />} />
<Route path="/notifications" element={<NotificationCourses />} />
<Route path="/id-verification/*" element={<IdVerificationPageSlot />} />
<Route path="/" element={<AccountSettingsPage />} />
<Route path="/notfound" element={<NotFoundPage />} />
...
);
}); |
Refactoring
2U uses IDV on edx.org for the Name Affirmation feature, which verifies certain name changes requested by certificate-bearing learners using IDV. This is because a name change has the potential to change the name displayed on their certificate(s). On the backend, this is implemented by the edx-name-affirmation plugin, and, on the frontend, this is implemented by the NameChange component in the frontend-app-account MFE.
The VerifiedName model has a verification_attempt_id field, which currently stores the id
field of the corresponding SoftwareSecurePhotoVerification instance used to verify a particular name. This field poses a problem for the introduction of an additional IDV implementation because the value of this field on any given row could also refer to the id
field of a Persona IDV record. It would not be possible to determine whether a given id
value refers to a SoftwareSecurePhotoVerification model instance or a Persona IDV record model instance.
We propose amending the IDVerificationAttempt model to add a uuid
field, which would be unique across all IDV-related model instances. 2U would also add a uuid
field to the Persona IDV record model. This uuid
would become the ID that the edX platform uses to uniquely refer to IDV records. Some views and APIs would need to be updated as a result of this changeIDV flow.
Event Hooks
We propose introducing event hooks related to the IDV attempt lifecycle. Taking the EXAM_ATTEMPT_*
Open edX events as a model, we propose introducing one Open edX event per status of the generic VerificationAttempt
model.
IDV_ATTEMPT_CREATED
IDV_ATTEMPT_PENDING
IDV_ATTEMPT_APPROVED
IDV_ATTEMPT_DENIED
The actual shape of the event data could be determined later.
Frontend Extensibility Mechanisms
We propose using the frontend-plugin-framework to integrate additional IDV implementations into the frontend-app-account MFE.
The current IDV flow is hosted in the frontend-app-account MFE. Although it works well, it is incompatible with Persona’s IDV flow. The Persona application collects all PII, government ID photos, and selfie photos (i.e. portrait photos) directly in the application, but edX’s current IDV flow performs all of those functions and submits the collected photos to Software Secure via the LMS.
For this reason, we need to replace the current IDV flow. In order to do this without modifying the current IDV flow hosted in the frontend-app-account MFE, we propose the addition of a PluginSlot from the frontend-plugin-framework to the frontend-app-account MFE. This PluginSlot would wrap the use of the IdVerificationPage in the id-verification
Route
. This would allow us to replace the entire IDV component with a new Persona integration component without needing to modify the route or the router.
There are a few other options for where to add a PluginSlot. These are documented in Alternative PluginSlot Locations.
Sample code is shown below for illustrative purposes.
Code Block |
---|
const IdVerificationPageSlot = () => (
<PluginSlot
id="id_verification_slot"
pluginProps={{
courseId,
}}
/>
<IdVerificationPage />
</PluginSlot>
)
...
subscribe(APP_READY, () => {
ReactDOM.render(
...
<Route path="/notifications/:courseId" element={<NotificationPreferences />} />
<Route path="/notifications" element={<NotificationCourses />} />
<Route path="/id-verification/*" element={<IdVerificationPageSlot />} />
<Route path="/" element={<AccountSettingsPage />} />
<Route path="/notfound" element={<NotFoundPage />} />
...
);
}); |
Refactoring
2U uses IDV on edx.org for the Name Affirmation feature, which verifies certain name changes requested by certificate-bearing learners using IDV. This is because a name change has the potential to change the name displayed on their certificate(s). On the backend, this is implemented by the edx-name-affirmation plugin, and, on the frontend, this is implemented by the NameChange component in the frontend-app-account MFE.
IDV Attempt References
The VerifiedName model has a verification_attempt_id field, which currently stores the id
field of the corresponding SoftwareSecurePhotoVerification instance used to verify a particular name. This field poses a problem for the introduction of an additional IDV implementation because the value of this field on any given row could also refer to the id
field of a Persona IDV record. It would not be possible to determine whether a given id
value refers to a SoftwareSecurePhotoVerification model instance or a Persona IDV record model instance.
We propose amending the IDVerificationAttempt model to add a verification_attempt
field, which will be a ForeignKey
field to the generic VerificationAttempt
model described above.
Support Tools Verified Name Panel
The frontend-app-support-tools contains a VerifiedName panel for displaying a learner’s verified name and the history of previous verified names. In the modal that opens when viewing the history, each VerifiedName is displayed in a table, and the verification_attempt_id field described above is shown in the “IDV Attempt ID” column.
When hovering over an ID in the column, the status of the corresponding IDV attempt (i.e. of the SoftwareSecurePhotoVerification
modal instance) is shown. The status is retrieved from the IDVerificationSupportView view of the user_api Django application. This view uses the get_verification_details_by_id method of the IDVerificationService, which queries the SoftwareSecurePhotoVerification
, SSOVerification
, and ManualVerification
models by their id
. This works due to inheritance from the abstract base class IDVerificationAttempt
.
With the introduction of the VerificationAttempt
model, an id
cannot uniquely identify a instance of the VerificationAttempt
, SoftwareSecurePhotoVerification
, SSOVerification
, and ManualVerification
models. This means that the get_verification_details_by_id method of the IDVerificationService will no longer work, breaking the Support Tools.
As a solution, the VerifiedName panel will be refactored to pull the IDV attempt status from the edx-name-affirmation application. The edx-name-affirmation Django application will refactor its VerifiedNameHistoryView view to pull this data by querying the model associated with either the verification_attempt_id
or verification_attempt
fields. As a result, the use of the VerifiedNameHistoryView view and the get_verification_details_by_id method of the IDVerificationService can be removed.
Name Change Signal Handler
Currently, the edx-name-affirmation Django application associates VerifiedNames with IDV attempts via the verification_attempt_id field. This association is formed by a task that is triggered by a signal handler that listens for a signal emitted by the SoftwareSecurePhotoVerification model. This means that only Software Secure IDV attempts can change the status of VerifiedNames (e.g. approve or deny them).
We propose triggering this task on the event hooks listed above. The impact of this change is that any form of IDV can change the status of VerifiedNames (e.g. approve or deny them). For example, an instance of the ManualVerification
class can be used to approve or deny a VerifiedName.
To handle deletes, the idv_delete_handler will be connected to the VerificationAttempt
post_delete
signal as it is for the SoftwareSecurePhotoVerification model.
Name Change Modal
We propose modifying the copy in the name change modal to make the instructions more generic. Some IDV implementations do not support non-government IDs.
We propose the following text.
Enter your name as it appears on your identification card.
Other Approaches Considered
...