Summary
I’m proposing a method to customize the access control in Open edX via plugins. This proposal is concerned about augmenting the course load
and enroll
permissions by providing django app and registering it using the Django settings.
Access Control in Open edX
Enrollment access control is pretty much hard-coded in Open edX. The course view and enrollment are not an exception.
Often times, when the access control logic need to be changed, the modifications are also hardcoded
which adds to the tech-debt that fork maintainers needs to carry on.
One example is the ability to make a Course Access Group feature which is something that
a couple of Open edX providers have expressed interest in and also being requested by our customers.
Pluggable Access Control
I’m trying to collect some architecture feedback before going and making such change in Open edX,
but mostly I’m interested in stealing from the LTI Consumer XBlock extensions architecture.
You can also check original pull request and how an LTI Plugin looks like.
If you’re really into it check other related work in Open edX:
- The registration extension form.
- The original hard-coded Course Access Group that we developed for a customer in 2015.
Focus oncan_enroll()
changes.
Proposal
There’s a couple of ways to design the extension, I’m going for the most simple one that can work focusing on the _has_access_course
function.
Hypothetically, if we’d like to make the enrollment in the course-v1:Uni+Demo+2020
course
to be exclusive for users with@example.edu
email we’d use the following configuration:
# my-configs/server-vars.yml
ACCESS_CONTROL_BACKENDS:
course_enroll:
NAME: 'my_access_plugins.backends.domain_based_course_enrollment'
OPTIONS:
enforce_domain_name_check: true
course_load:
NAME: 'my_access_plugins.backends.domain_based_course_enrollment'
And the backend would look like this:
# my_access_plugins/backends.py
def domain_based_course_enrollment(user, courselike, default_access, options):
if courselike.id == 'course-v1:Uni+Demo+2020' and not user.email.endswith('@example.edu'):
return False
if options.get('enforce_domain_name_check', False):
# Check the DNS records for such email.
pass
return default_access
Some Implementation Example
While the actual implementation will be different, here’s an approximate idea of what would it look like:
The settings would follow the same patten like other Open edX settings.
# lms/envs/aws.py
ACCESS_CONTROL_BACKENDS = ENV_TOKENS.get('ACCESS_CONTROL_BACKENDS', {})
We also need a helper to handle the common logic for invoking the backends:
# somewhere/helpers.py
def use_acl_backend(backend_name, user, resource, default_access):
# Not doing too much error handling at the moment
backend_path = ACCESS_CONTROL_BACKENDS.get(backend_name).get('NAME')
backend_options = ACCESS_CONTROL_BACKENDS.get(backend_name).get('OPTIONS', {})
if backend_path:
backend = import_module(module_path)
return backend(user, resource, default_access=default_access, options=backend_options)
return default_access
This would be the actual use of the backends, it needs to put it in other places in the platform.
# lms/djangoapps/courseware/access.py
def can_enroll(user, course):
# Over-simplified function, since the actual `access.py` is pretty complex.
# ...
can_enroll = through_some_other_logic()
# ...
#
return use_acl_backend('course_enroll', user, resource=course, default_access=can_enroll)
Let me know if you’d like me to expand with more details.
Questions
- Tell me what do you think?
- How can we get this into Open edX?
- Do you see any issues with the proposal above?
- Any better alternative do you have in mind?
- Is this change has enough impact to require an OEP?