New provider config options, New Institution Login Menu - PR 8603
This commit is contained in:
@@ -1498,6 +1498,13 @@ def create_account_with_params(request, params):
|
||||
|
||||
dog_stats_api.increment("common.student.account_created")
|
||||
|
||||
# If the user is registering via 3rd party auth, track which provider they use
|
||||
third_party_provider = None
|
||||
running_pipeline = None
|
||||
if third_party_auth.is_enabled() and pipeline.running(request):
|
||||
running_pipeline = pipeline.get(request)
|
||||
third_party_provider = provider.Registry.get_from_pipeline(running_pipeline)
|
||||
|
||||
# Track the user's registration
|
||||
if settings.FEATURES.get('SEGMENT_IO_LMS') and hasattr(settings, 'SEGMENT_IO_LMS_KEY'):
|
||||
tracking_context = tracker.get_tracker().resolve_context()
|
||||
@@ -1506,20 +1513,13 @@ def create_account_with_params(request, params):
|
||||
'username': user.username,
|
||||
})
|
||||
|
||||
# If the user is registering via 3rd party auth, track which provider they use
|
||||
provider_name = None
|
||||
if third_party_auth.is_enabled() and pipeline.running(request):
|
||||
running_pipeline = pipeline.get(request)
|
||||
current_provider = provider.Registry.get_from_pipeline(running_pipeline)
|
||||
provider_name = current_provider.name
|
||||
|
||||
analytics.track(
|
||||
user.id,
|
||||
"edx.bi.user.account.registered",
|
||||
{
|
||||
'category': 'conversion',
|
||||
'label': params.get('course_id'),
|
||||
'provider': provider_name
|
||||
'provider': third_party_provider.name if third_party_provider else None
|
||||
},
|
||||
context={
|
||||
'Google Analytics': {
|
||||
@@ -1536,6 +1536,7 @@ def create_account_with_params(request, params):
|
||||
# 2. Random user generation for other forms of testing.
|
||||
# 3. External auth bypassing activation.
|
||||
# 4. Have the platform configured to not require e-mail activation.
|
||||
# 5. Registering a new user using a trusted third party provider (with skip_email_verification=True)
|
||||
#
|
||||
# Note that this feature is only tested as a flag set one way or
|
||||
# the other for *new* systems. we need to be careful about
|
||||
@@ -1544,7 +1545,11 @@ def create_account_with_params(request, params):
|
||||
send_email = (
|
||||
not settings.FEATURES.get('SKIP_EMAIL_VALIDATION', None) and
|
||||
not settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING') and
|
||||
not (do_external_auth and settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'))
|
||||
not (do_external_auth and settings.FEATURES.get('BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH')) and
|
||||
not (
|
||||
third_party_provider and third_party_provider.skip_email_verification and
|
||||
user.email == running_pipeline['kwargs'].get('details', {}).get('email')
|
||||
)
|
||||
)
|
||||
if send_email:
|
||||
context = {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from south.utils import datetime_utils as datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding field 'SAMLProviderConfig.secondary'
|
||||
db.add_column('third_party_auth_samlproviderconfig', 'secondary',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'SAMLProviderConfig.skip_registration_form'
|
||||
db.add_column('third_party_auth_samlproviderconfig', 'skip_registration_form',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'SAMLProviderConfig.skip_email_verification'
|
||||
db.add_column('third_party_auth_samlproviderconfig', 'skip_email_verification',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'OAuth2ProviderConfig.secondary'
|
||||
db.add_column('third_party_auth_oauth2providerconfig', 'secondary',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'OAuth2ProviderConfig.skip_registration_form'
|
||||
db.add_column('third_party_auth_oauth2providerconfig', 'skip_registration_form',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'OAuth2ProviderConfig.skip_email_verification'
|
||||
db.add_column('third_party_auth_oauth2providerconfig', 'skip_email_verification',
|
||||
self.gf('django.db.models.fields.BooleanField')(default=False),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'SAMLProviderConfig.secondary'
|
||||
db.delete_column('third_party_auth_samlproviderconfig', 'secondary')
|
||||
|
||||
# Deleting field 'SAMLProviderConfig.skip_registration_form'
|
||||
db.delete_column('third_party_auth_samlproviderconfig', 'skip_registration_form')
|
||||
|
||||
# Deleting field 'SAMLProviderConfig.skip_email_verification'
|
||||
db.delete_column('third_party_auth_samlproviderconfig', 'skip_email_verification')
|
||||
|
||||
# Deleting field 'OAuth2ProviderConfig.secondary'
|
||||
db.delete_column('third_party_auth_oauth2providerconfig', 'secondary')
|
||||
|
||||
# Deleting field 'OAuth2ProviderConfig.skip_registration_form'
|
||||
db.delete_column('third_party_auth_oauth2providerconfig', 'skip_registration_form')
|
||||
|
||||
# Deleting field 'OAuth2ProviderConfig.skip_email_verification'
|
||||
db.delete_column('third_party_auth_oauth2providerconfig', 'skip_email_verification')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'third_party_auth.oauth2providerconfig': {
|
||||
'Meta': {'object_name': 'OAuth2ProviderConfig'},
|
||||
'backend_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'db_index': 'True'}),
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'icon_class': ('django.db.models.fields.CharField', [], {'default': "'fa-sign-in'", 'max_length': '50'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'key': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
|
||||
'other_settings': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'secondary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'secret': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'skip_email_verification': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'skip_registration_form': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'third_party_auth.samlconfiguration': {
|
||||
'Meta': {'object_name': 'SAMLConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'entity_id': ('django.db.models.fields.CharField', [], {'default': "'http://saml.example.com'", 'max_length': '255'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'org_info_str': ('django.db.models.fields.TextField', [], {'default': '\'{"en-US": {"url": "http://www.example.com", "displayname": "Example Inc.", "name": "example"}}\''}),
|
||||
'other_config_str': ('django.db.models.fields.TextField', [], {'default': '\'{\\n"SECURITY_CONFIG": {"metadataCacheDuration": 604800, "signMetadata": false}\\n}\''}),
|
||||
'private_key': ('django.db.models.fields.TextField', [], {}),
|
||||
'public_key': ('django.db.models.fields.TextField', [], {})
|
||||
},
|
||||
'third_party_auth.samlproviderconfig': {
|
||||
'Meta': {'object_name': 'SAMLProviderConfig'},
|
||||
'attr_email': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'attr_first_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'attr_full_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'attr_last_name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'attr_user_permanent_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'attr_username': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}),
|
||||
'backend_name': ('django.db.models.fields.CharField', [], {'default': "'tpa-saml'", 'max_length': '50'}),
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'entity_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'icon_class': ('django.db.models.fields.CharField', [], {'default': "'fa-sign-in'", 'max_length': '50'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'idp_slug': ('django.db.models.fields.SlugField', [], {'max_length': '30'}),
|
||||
'metadata_source': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
|
||||
'other_settings': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'secondary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'skip_email_verification': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'skip_registration_form': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
|
||||
},
|
||||
'third_party_auth.samlproviderdata': {
|
||||
'Meta': {'ordering': "('-fetched_at',)", 'object_name': 'SAMLProviderData'},
|
||||
'entity_id': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'expires_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
|
||||
'fetched_at': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'public_key': ('django.db.models.fields.TextField', [], {}),
|
||||
'sso_url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['third_party_auth']
|
||||
@@ -8,7 +8,7 @@ from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import json
|
||||
import logging
|
||||
from social.backends.base import BaseAuth
|
||||
@@ -54,7 +54,7 @@ class AuthNotConfigured(SocialAuthBaseException):
|
||||
self.provider_name = provider_name
|
||||
|
||||
def __str__(self):
|
||||
return _('Authentication with {} is currently unavailable.').format(
|
||||
return _('Authentication with {} is currently unavailable.').format( # pylint: disable=no-member
|
||||
self.provider_name
|
||||
)
|
||||
|
||||
@@ -68,10 +68,34 @@ class ProviderConfig(ConfigurationModel):
|
||||
help_text=(
|
||||
'The Font Awesome (or custom) icon class to use on the login button for this provider. '
|
||||
'Examples: fa-google-plus, fa-facebook, fa-linkedin, fa-sign-in, fa-university'
|
||||
))
|
||||
),
|
||||
)
|
||||
name = models.CharField(max_length=50, blank=False, help_text="Name of this provider (shown to users)")
|
||||
secondary = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
'Secondary providers are displayed less prominently, '
|
||||
'in a separate list of "Institution" login providers.'
|
||||
),
|
||||
)
|
||||
skip_registration_form = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
"If this option is enabled, users will not be asked to confirm their details "
|
||||
"(name, email, etc.) during the registration process. Only select this option "
|
||||
"for trusted providers that are known to provide accurate user information."
|
||||
),
|
||||
)
|
||||
skip_email_verification = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
"If this option is selected, users will not be required to confirm their "
|
||||
"email, and their account will be activated immediately upon registration."
|
||||
),
|
||||
)
|
||||
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
|
||||
|
||||
# "enabled" field is inherited from ConfigurationModel
|
||||
|
||||
class Meta(object): # pylint: disable=missing-docstring
|
||||
|
||||
@@ -503,12 +503,19 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia
|
||||
"""Redirects to the registration page."""
|
||||
return redirect(AUTH_DISPATCH_URLS[AUTH_ENTRY_REGISTER])
|
||||
|
||||
def should_force_account_creation():
|
||||
""" For some third party providers, we auto-create user accounts """
|
||||
current_provider = provider.Registry.get_from_pipeline({'backend': backend.name, 'kwargs': kwargs})
|
||||
return current_provider and current_provider.skip_email_verification
|
||||
|
||||
if not user:
|
||||
if auth_entry in [AUTH_ENTRY_LOGIN_API, AUTH_ENTRY_REGISTER_API]:
|
||||
return HttpResponseBadRequest()
|
||||
elif auth_entry in [AUTH_ENTRY_LOGIN, AUTH_ENTRY_LOGIN_2]:
|
||||
# User has authenticated with the third party provider but we don't know which edX
|
||||
# account corresponds to them yet, if any.
|
||||
if should_force_account_creation():
|
||||
return dispatch_to_register()
|
||||
return dispatch_to_login()
|
||||
elif auth_entry in [AUTH_ENTRY_REGISTER, AUTH_ENTRY_REGISTER_2]:
|
||||
# User has authenticated with the third party provider and now wants to finish
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Third_party_auth integration tests using a mock version of the TestShib provider
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
import httpretty
|
||||
from mock import patch
|
||||
@@ -62,8 +63,6 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('Authentication with TestShib is currently unavailable.', response.content)
|
||||
|
||||
# Note: the following patch is only needed until https://github.com/edx/edx-platform/pull/8262 is merged
|
||||
@patch.dict("django.conf.settings.FEATURES", {"AUTOMATIC_AUTH_FOR_TESTING": True})
|
||||
def test_register(self):
|
||||
self._configure_testshib_provider()
|
||||
self._freeze_time(timestamp=1434326820) # This is the time when the saved request/response was recorded.
|
||||
@@ -107,6 +106,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
|
||||
|
||||
# Now check that we can login again:
|
||||
self.client.logout()
|
||||
self._verify_user_email('myself@testshib.org')
|
||||
self._test_return_login()
|
||||
|
||||
def test_login(self):
|
||||
@@ -222,3 +222,9 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
|
||||
content_type='application/x-www-form-urlencoded',
|
||||
data=self._read_data_file('testshib_response.txt'),
|
||||
)
|
||||
|
||||
def _verify_user_email(self, email):
|
||||
""" Mark the user with the given email as verified """
|
||||
user = User.objects.get(email=email)
|
||||
user.is_active = True
|
||||
user.save()
|
||||
|
||||
Reference in New Issue
Block a user