Merge pull request #22292 from edx/hasnain-naveed/ENT-2460
ENT-2460 | Added a model for whitelist emails for authentication.
This commit is contained in:
@@ -26,6 +26,7 @@ from openedx.core.lib.courses import clean_course_id
|
||||
from student import STUDENT_WAFFLE_NAMESPACE
|
||||
from student.models import (
|
||||
AccountRecovery,
|
||||
AllowedAuthUser,
|
||||
CourseAccessRole,
|
||||
CourseEnrollment,
|
||||
CourseEnrollmentAllowed,
|
||||
@@ -441,6 +442,48 @@ class LoginFailuresAdmin(admin.ModelAdmin):
|
||||
self.model.clear_lockout_counter(obj.user)
|
||||
|
||||
|
||||
class AllowedAuthUserForm(forms.ModelForm):
|
||||
"""Model Form for AllowedAuthUser model's admin interface."""
|
||||
|
||||
class Meta(object):
|
||||
model = AllowedAuthUser
|
||||
fields = ('site', 'email', )
|
||||
|
||||
def clean_email(self):
|
||||
"""
|
||||
Validate the email field.
|
||||
"""
|
||||
email = self.cleaned_data['email']
|
||||
email_domain = email.split('@')[-1]
|
||||
allowed_site_email_domain = self.cleaned_data['site'].configuration.get_value('THIRD_PARTY_AUTH_ONLY_DOMAIN')
|
||||
|
||||
if not allowed_site_email_domain:
|
||||
raise forms.ValidationError(
|
||||
_("Please add a key/value 'THIRD_PARTY_AUTH_ONLY_DOMAIN/{site_email_domain}' in SiteConfiguration "
|
||||
"model's values field.")
|
||||
)
|
||||
elif email_domain != allowed_site_email_domain:
|
||||
raise forms.ValidationError(
|
||||
_("Email doesn't have {domain_name} domain name.".format(domain_name=allowed_site_email_domain))
|
||||
)
|
||||
elif not User.objects.filter(email=email).exists():
|
||||
raise forms.ValidationError(_("User with this email doesn't exist in system."))
|
||||
else:
|
||||
return email
|
||||
|
||||
|
||||
@admin.register(AllowedAuthUser)
|
||||
class AllowedAuthUserAdmin(admin.ModelAdmin):
|
||||
""" Admin interface for the AllowedAuthUser model. """
|
||||
form = AllowedAuthUserForm
|
||||
list_display = ('email', 'site',)
|
||||
search_fields = ('email',)
|
||||
ordering = ('-created',)
|
||||
|
||||
class Meta(object):
|
||||
model = AllowedAuthUser
|
||||
|
||||
|
||||
admin.site.register(UserTestGroup)
|
||||
admin.site.register(Registration)
|
||||
admin.site.register(PendingNameChange)
|
||||
|
||||
32
common/djangoapps/student/migrations/0026_allowedauthuser.py
Normal file
32
common/djangoapps/student/migrations/0026_allowedauthuser.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.26 on 2019-11-14 14:12
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import model_utils.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sites', '0002_alter_domain_unique'),
|
||||
('student', '0025_auto_20191101_1846'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AllowedAuthUser',
|
||||
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')),
|
||||
('email', models.EmailField(help_text="An employee (a user whose email has current site's domain name) whose email exists in this model, can be able to login from login screen through email and password. And if any employee's email doesn't exist in this model then that employee can login via third party authentication backend only.", max_length=254, unique=True)),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='allowed_auth_users', to='sites.Site')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -29,6 +29,7 @@ from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.signals import user_logged_in, user_logged_out
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
|
||||
from django.db import IntegrityError, models
|
||||
@@ -2966,3 +2967,14 @@ class AccountRecovery(models.Model):
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AllowedAuthUser(TimeStampedModel):
|
||||
site = models.ForeignKey(Site, related_name='allowed_auth_users', on_delete=models.CASCADE)
|
||||
email = models.EmailField(
|
||||
help_text=_(
|
||||
"An employee (a user whose email has current site's domain name) whose email exists in this model, can be "
|
||||
"able to login from login screen through email and password. And if any employee's email doesn't exist in "
|
||||
"this model then that employee can login via third party authentication backend only."),
|
||||
unique=True,
|
||||
)
|
||||
|
||||
@@ -18,12 +18,13 @@ from django.utils.timezone import now
|
||||
from mock import Mock
|
||||
from pytz import UTC
|
||||
|
||||
from student.admin import COURSE_ENROLLMENT_ADMIN_SWITCH, UserAdmin, CourseEnrollmentForm
|
||||
from student.models import CourseEnrollment, LoginFailures
|
||||
from student.admin import AllowedAuthUserForm, COURSE_ENROLLMENT_ADMIN_SWITCH, UserAdmin, CourseEnrollmentForm
|
||||
from student.models import AllowedAuthUser, CourseEnrollment, LoginFailures
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
|
||||
|
||||
|
||||
class AdminCourseRolesPageTest(SharedModuleStoreTestCase):
|
||||
@@ -435,3 +436,96 @@ class CourseEnrollmentAdminFormTest(SharedModuleStoreTestCase):
|
||||
self.assertEqual(count, CourseEnrollment.objects.count())
|
||||
self.assertFalse(course_enrollment.is_active)
|
||||
self.assertEqual(enrollment.id, course_enrollment.id)
|
||||
|
||||
|
||||
class AllowedAuthUserFormTest(SiteMixin, TestCase):
|
||||
"""
|
||||
Unit test for AllowedAuthUserAdmin's ModelForm.
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(AllowedAuthUserFormTest, cls).setUpClass()
|
||||
cls.email_domain_name = "dummy.com"
|
||||
cls.email_with_wrong_domain = "dummy@example.com"
|
||||
cls.valid_email = "dummy@{email_domain_name}".format(email_domain_name=cls.email_domain_name)
|
||||
cls.other_valid_email = "dummy1@{email_domain_name}".format(email_domain_name=cls.email_domain_name)
|
||||
UserFactory(email=cls.valid_email)
|
||||
UserFactory(email=cls.email_with_wrong_domain)
|
||||
|
||||
def _update_site_configuration(self):
|
||||
""" Updates the site's configuration """
|
||||
self.site.configuration.values = {'THIRD_PARTY_AUTH_ONLY_DOMAIN': self.email_domain_name}
|
||||
self.site.configuration.save()
|
||||
|
||||
def _assert_form(self, site, email, is_valid_form=False):
|
||||
"""
|
||||
Asserts the form and returns the error if its not valid and instance if its valid
|
||||
"""
|
||||
error = ''
|
||||
instance = None
|
||||
form = AllowedAuthUserForm({'site': site.id, 'email': email})
|
||||
if is_valid_form:
|
||||
self.assertTrue(form.is_valid())
|
||||
instance = form.save()
|
||||
else:
|
||||
self.assertFalse(form.is_valid())
|
||||
error = form.errors['email'][0]
|
||||
return error, instance
|
||||
|
||||
def test_form_with_invalid_site_configuration(self):
|
||||
"""
|
||||
Test form with wrong site's configuration.
|
||||
"""
|
||||
error, _ = self._assert_form(self.site, self.valid_email)
|
||||
self.assertEqual(
|
||||
error,
|
||||
"Please add a key/value 'THIRD_PARTY_AUTH_ONLY_DOMAIN/{site_email_domain}' in SiteConfiguration "
|
||||
"model's values field."
|
||||
)
|
||||
|
||||
def test_form_with_invalid_domain_name(self):
|
||||
"""
|
||||
Test form with email which has wrong email domain.
|
||||
"""
|
||||
self._update_site_configuration()
|
||||
error, _ = self._assert_form(self.site, self.email_with_wrong_domain)
|
||||
self.assertEqual(
|
||||
error,
|
||||
"Email doesn't have {email_domain_name} domain name.".format(email_domain_name=self.email_domain_name)
|
||||
)
|
||||
|
||||
def test_form_with_invalid_user(self):
|
||||
"""
|
||||
Test form with an email which is not associated with any user.
|
||||
"""
|
||||
self._update_site_configuration()
|
||||
error, _ = self._assert_form(self.site, self.other_valid_email)
|
||||
self.assertEqual(error, "User with this email doesn't exist in system.")
|
||||
|
||||
def test_form_creation(self):
|
||||
"""
|
||||
Test AllowedAuthUserForm creation.
|
||||
"""
|
||||
self._update_site_configuration()
|
||||
_, allowed_auth_user = self._assert_form(self.site, self.valid_email, is_valid_form=True)
|
||||
db_allowed_auth_user = AllowedAuthUser.objects.all().first()
|
||||
self.assertEqual(db_allowed_auth_user.site.id, allowed_auth_user.site.id)
|
||||
self.assertEqual(db_allowed_auth_user.email, allowed_auth_user.email)
|
||||
|
||||
def test_form_update(self):
|
||||
"""
|
||||
Test AllowedAuthUserForm update.
|
||||
"""
|
||||
self._update_site_configuration()
|
||||
UserFactory(email=self.other_valid_email)
|
||||
_, allowed_auth_user = self._assert_form(self.site, self.valid_email, is_valid_form=True)
|
||||
self.assertEqual(AllowedAuthUser.objects.all().count(), 1)
|
||||
|
||||
# update the object with new instance.
|
||||
form = AllowedAuthUserForm({'site': self.site.id, 'email': self.other_valid_email}, instance=allowed_auth_user)
|
||||
self.assertTrue(form.is_valid())
|
||||
form.save()
|
||||
|
||||
db_allowed_auth_user = AllowedAuthUser.objects.all().first()
|
||||
self.assertEqual(AllowedAuthUser.objects.all().count(), 1)
|
||||
self.assertEqual(db_allowed_auth_user.email, self.other_valid_email)
|
||||
|
||||
Reference in New Issue
Block a user