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:
Hasnain Naveed
2019-11-15 14:37:44 +05:00
committed by GitHub
4 changed files with 183 additions and 2 deletions

View File

@@ -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)

View 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,
},
),
]

View File

@@ -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,
)

View File

@@ -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)