ENT-944 Create SSOVerifications for users in tpa pipeline based on provider's settings
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-11 15:33
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('third_party_auth', '0020_cleanup_slug_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ltiproviderconfig',
|
||||
name='enable_sso_id_verification',
|
||||
field=models.BooleanField(default=False, help_text=b'Use the presence of a profile from a trusted third party as proof of identity verification.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauth2providerconfig',
|
||||
name='enable_sso_id_verification',
|
||||
field=models.BooleanField(default=False, help_text=b'Use the presence of a profile from a trusted third party as proof of identity verification.'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='samlproviderconfig',
|
||||
name='enable_sso_id_verification',
|
||||
field=models.BooleanField(default=False, help_text=b'Use the presence of a profile from a trusted third party as proof of identity verification.'),
|
||||
),
|
||||
]
|
||||
@@ -196,6 +196,10 @@ class ProviderConfig(ConfigurationModel):
|
||||
"with their account is changed as a part of this synchronization."
|
||||
)
|
||||
)
|
||||
enable_sso_id_verification = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Use the presence of a profile from a trusted third party as proof of identity verification.",
|
||||
)
|
||||
prefix = None # used for provider_id. Set to a string value in subclass
|
||||
backend_name = None # Set to a field or fixed value in subclass
|
||||
accepts_logins = True # Whether to display a sign-in button when the provider is enabled
|
||||
@@ -223,6 +227,11 @@ class ProviderConfig(ConfigurationModel):
|
||||
""" Get the python-social-auth backend class used for this provider """
|
||||
return _PSA_BACKENDS[self.backend_name]
|
||||
|
||||
@property
|
||||
def full_class_name(self):
|
||||
""" Get the fully qualified class name of this provider. """
|
||||
return '{}.{}'.format(self.__module__, self.__class__.__name__)
|
||||
|
||||
def get_url_params(self):
|
||||
""" Get a dict of GET parameters to append to login links for this provider """
|
||||
return {}
|
||||
|
||||
@@ -83,6 +83,8 @@ from edxmako.shortcuts import render_to_string
|
||||
from eventtracking import tracker
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from third_party_auth.utils import user_exists
|
||||
from lms.djangoapps.verify_student.models import SSOVerification
|
||||
from lms.djangoapps.verify_student.utils import earliest_allowed_verification_date
|
||||
|
||||
from . import provider
|
||||
|
||||
@@ -774,3 +776,29 @@ def user_details_force_sync(auth_entry, strategy, details, user=None, *args, **k
|
||||
except SMTPException:
|
||||
logger.exception('Error sending IdP learner data sync-initiated email change '
|
||||
'notification email for user [%s].', user.username)
|
||||
|
||||
|
||||
def set_id_verification_status(auth_entry, strategy, details, user=None, *args, **kwargs):
|
||||
"""
|
||||
Use the user's authentication with the provider, if configured, as evidence of their identity being verified.
|
||||
"""
|
||||
current_provider = provider.Registry.get_from_pipeline({'backend': strategy.request.backend.name, 'kwargs': kwargs})
|
||||
if user and current_provider.enable_sso_id_verification:
|
||||
# Get previous valid, non expired verification attempts for this SSO Provider and user
|
||||
verifications = SSOVerification.objects.filter(
|
||||
user=user,
|
||||
status="approved",
|
||||
created_at__gte=earliest_allowed_verification_date(),
|
||||
identity_provider_type=current_provider.full_class_name,
|
||||
identity_provider_slug=current_provider.slug,
|
||||
)
|
||||
|
||||
# If there is none, create a new approved verification for the user.
|
||||
if not verifications:
|
||||
SSOVerification.objects.create(
|
||||
user=user,
|
||||
status="approved",
|
||||
name=user.profile.name,
|
||||
identity_provider_type=current_provider.full_class_name,
|
||||
identity_provider_slug=current_provider.slug,
|
||||
)
|
||||
|
||||
@@ -58,6 +58,7 @@ def apply_settings(django_settings):
|
||||
'social_core.pipeline.social_auth.load_extra_data',
|
||||
'social_core.pipeline.user.user_details',
|
||||
'third_party_auth.pipeline.user_details_force_sync',
|
||||
'third_party_auth.pipeline.set_id_verification_status',
|
||||
'third_party_auth.pipeline.set_logged_in_cookies',
|
||||
'third_party_auth.pipeline.login_analytics',
|
||||
]
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import datetime
|
||||
import mock
|
||||
import pytz
|
||||
import ddt
|
||||
from django import test
|
||||
from django.contrib.auth import models
|
||||
@@ -12,6 +14,7 @@ from social_django import models as social_models
|
||||
from student.tests.factories import UserFactory
|
||||
from third_party_auth import pipeline, provider
|
||||
from third_party_auth.tests import testutil
|
||||
from lms.djangoapps.verify_student.models import SSOVerification
|
||||
|
||||
# Get Django User model by reference from python-social-auth. Not a type
|
||||
# constant, pylint.
|
||||
@@ -440,3 +443,101 @@ class UserDetailsForceSyncTestCase(testutil.TestCase, test.TestCase):
|
||||
|
||||
# An email should still be sent because the email changed.
|
||||
assert len(mail.outbox) == 1
|
||||
|
||||
|
||||
@unittest.skipUnless(testutil.AUTH_FEATURE_ENABLED, testutil.AUTH_FEATURES_KEY + ' not enabled')
|
||||
class SetIDVerificationStatusTestCase(testutil.TestCase, test.TestCase):
|
||||
"""Tests to ensure SSO ID Verification for the user is set if the provider requires it."""
|
||||
|
||||
def setUp(self):
|
||||
super(SetIDVerificationStatusTestCase, self).setUp()
|
||||
self.user = UserFactory.create()
|
||||
self.provider_class_name = 'third_party_auth.models.SAMLProviderConfig'
|
||||
self.provider_slug = 'default'
|
||||
self.details = {}
|
||||
|
||||
# Mocks
|
||||
self.strategy = mock.MagicMock()
|
||||
self.strategy.storage.user.changed.side_effect = lambda user: user.save()
|
||||
|
||||
get_from_pipeline = mock.patch('third_party_auth.pipeline.provider.Registry.get_from_pipeline')
|
||||
self.get_from_pipeline = get_from_pipeline.start()
|
||||
self.get_from_pipeline.return_value = mock.MagicMock(
|
||||
enable_sso_id_verification=True,
|
||||
full_class_name=self.provider_class_name,
|
||||
slug=self.provider_slug,
|
||||
)
|
||||
self.addCleanup(get_from_pipeline.stop)
|
||||
|
||||
def test_set_id_verification_status_new_user(self):
|
||||
"""
|
||||
The user details are synced properly and an email is sent when the email is changed.
|
||||
"""
|
||||
# Begin the pipeline.
|
||||
pipeline.set_id_verification_status(
|
||||
auth_entry=pipeline.AUTH_ENTRY_LOGIN,
|
||||
strategy=self.strategy,
|
||||
details=self.details,
|
||||
user=self.user,
|
||||
)
|
||||
|
||||
verification = SSOVerification.objects.get(user=self.user)
|
||||
|
||||
assert verification.identity_provider_type == self.provider_class_name
|
||||
assert verification.identity_provider_slug == self.provider_slug
|
||||
assert verification.status == 'approved'
|
||||
assert verification.name == self.user.profile.name
|
||||
|
||||
def test_set_id_verification_status_returning_user(self):
|
||||
"""
|
||||
The user details are synced properly and an email is sent when the email is changed.
|
||||
"""
|
||||
|
||||
SSOVerification.objects.create(
|
||||
user=self.user,
|
||||
status="approved",
|
||||
name=self.user.profile.name,
|
||||
identity_provider_type=self.provider_class_name,
|
||||
identity_provider_slug=self.provider_slug,
|
||||
)
|
||||
|
||||
# Begin the pipeline.
|
||||
pipeline.set_id_verification_status(
|
||||
auth_entry=pipeline.AUTH_ENTRY_LOGIN,
|
||||
strategy=self.strategy,
|
||||
details=self.details,
|
||||
user=self.user,
|
||||
)
|
||||
|
||||
assert SSOVerification.objects.filter(user=self.user).count() == 1
|
||||
|
||||
def test_set_id_verification_status_expired(self):
|
||||
"""
|
||||
The user details are synced properly and an email is sent when the email is changed.
|
||||
"""
|
||||
|
||||
SSOVerification.objects.create(
|
||||
user=self.user,
|
||||
status="approved",
|
||||
name=self.user.profile.name,
|
||||
identity_provider_type=self.provider_class_name,
|
||||
identity_provider_slug=self.provider_slug,
|
||||
)
|
||||
|
||||
with mock.patch('third_party_auth.pipeline.earliest_allowed_verification_date') as earliest_date:
|
||||
earliest_date.return_value = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=1)
|
||||
# Begin the pipeline.
|
||||
pipeline.set_id_verification_status(
|
||||
auth_entry=pipeline.AUTH_ENTRY_LOGIN,
|
||||
strategy=self.strategy,
|
||||
details=self.details,
|
||||
user=self.user,
|
||||
)
|
||||
|
||||
assert SSOVerification.objects.filter(
|
||||
user=self.user,
|
||||
status="approved",
|
||||
name=self.user.profile.name,
|
||||
identity_provider_type=self.provider_class_name,
|
||||
identity_provider_slug=self.provider_slug,
|
||||
).count() == 2
|
||||
|
||||
@@ -5,7 +5,7 @@ Admin site configurations for verify_student.
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification, SSOVerification
|
||||
|
||||
|
||||
@admin.register(SoftwareSecurePhotoVerification)
|
||||
@@ -16,3 +16,14 @@ class SoftwareSecurePhotoVerificationAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'user', 'status', 'receipt_id', 'submitted_at', 'updated_at',)
|
||||
raw_id_fields = ('user', 'reviewing_user', 'copy_id_photo_from',)
|
||||
search_fields = ('receipt_id', 'user__username',)
|
||||
|
||||
|
||||
@admin.register(SSOVerification)
|
||||
class SSOVerificationAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin for the SSOVerification table.
|
||||
"""
|
||||
list_display = ('id', 'user', 'status', 'identity_provider_slug', 'created_at', 'updated_at',)
|
||||
readonly_fields = ('user', 'identity_provider_slug', 'identity_provider_type',)
|
||||
raw_id_fields = ('user',)
|
||||
search_fields = ('user__username', 'identity_provider_slug',)
|
||||
|
||||
Reference in New Issue
Block a user