Commit ce2ab5b1 by Jesse Shapiro

Adding 'visible' field for SSO providers

parent 6f0e0971
......@@ -194,7 +194,7 @@ def auth_pipeline_urls(auth_entry, redirect_url=None):
return {
provider.provider_id: third_party_auth.pipeline.get_login_url(
provider.provider_id, auth_entry, redirect_url=redirect_url
) for provider in third_party_auth.provider.Registry.accepting_logins()
) for provider in third_party_auth.provider.Registry.displayed_for_login()
}
......
......@@ -54,8 +54,8 @@ class LoginFormTest(ThirdPartyAuthTestMixin, UrlResetMixin, SharedModuleStoreTes
self.url = reverse("signin_user")
self.course_id = unicode(self.course.id)
self.courseware_url = reverse("courseware", args=[self.course_id])
self.configure_google_provider(enabled=True)
self.configure_facebook_provider(enabled=True)
self.configure_google_provider(enabled=True, visible=True)
self.configure_facebook_provider(enabled=True, visible=True)
@patch.dict(settings.FEATURES, {"ENABLE_THIRD_PARTY_AUTH": False})
@ddt.data(THIRD_PARTY_AUTH_PROVIDERS)
......@@ -170,8 +170,8 @@ class RegisterFormTest(ThirdPartyAuthTestMixin, UrlResetMixin, SharedModuleStore
self.url = reverse("register_user")
self.course_id = unicode(self.course.id)
self.configure_google_provider(enabled=True)
self.configure_facebook_provider(enabled=True)
self.configure_google_provider(enabled=True, visible=True)
self.configure_facebook_provider(enabled=True, visible=True)
@patch.dict(settings.FEATURES, {"ENABLE_THIRD_PARTY_AUTH": False})
@ddt.data(*THIRD_PARTY_AUTH_PROVIDERS)
......
......@@ -51,7 +51,11 @@ class TpaAPITestCase(ThirdPartyAuthTestMixin, APITestCase):
self.configure_facebook_provider(enabled=True)
self.configure_linkedin_provider(enabled=False)
self.enable_saml()
testshib = self.configure_saml_provider(name='TestShib', enabled=True, idp_slug=IDP_SLUG_TESTSHIB)
testshib = self.configure_saml_provider(
name='TestShib',
enabled=True,
idp_slug=IDP_SLUG_TESTSHIB
)
# Create several users and link each user to Google and TestShib
for username in LINKED_USERS:
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('third_party_auth', '0001_initial'),
('third_party_auth', '0002_schema__provider_icon_image'),
('third_party_auth', '0003_samlproviderconfig_debug_mode'),
]
operations = [
migrations.AddField(
model_name='LTIProviderConfig',
name='visible',
field=models.BooleanField(
help_text=b'If this option is not selected, users will not be presented with the provider as an option to authenticate with on the login screen, but manual authentication using the correct link is still possible.',
default=True
),
preserve_default=False
),
migrations.AlterField(
model_name='LTIProviderConfig',
name='visible',
field=models.BooleanField(
help_text=b'If this option is not selected, users will not be presented with the provider as an option to authenticate with on the login screen, but manual authentication using the correct link is still possible.',
default=False
)
),
migrations.AddField(
model_name='OAuth2ProviderConfig',
name='visible',
field=models.BooleanField(
help_text=b'If this option is not selected, users will not be presented with the provider as an option to authenticate with on the login screen, but manual authentication using the correct link is still possible.',
default=True
),
preserve_default=False
),
migrations.AlterField(
model_name='OAuth2ProviderConfig',
name='visible',
field=models.BooleanField(
help_text=b'If this option is not selected, users will not be presented with the provider as an option to authenticate with on the login screen, but manual authentication using the correct link is still possible.',
default=False
)
),
migrations.AddField(
model_name='SAMLProviderConfig',
name='visible',
field=models.BooleanField(
help_text=b'If this option is not selected, users will not be presented with the provider as an option to authenticate with on the login screen, but manual authentication using the correct link is still possible.',
default=True
),
preserve_default=False
),
migrations.AlterField(
model_name='SAMLProviderConfig',
name='visible',
field=models.BooleanField(
help_text=b'If this option is not selected, users will not be presented with the provider as an option to authenticate with on the login screen, but manual authentication using the correct link is still possible.',
default=False
)
),
]
......@@ -121,6 +121,14 @@ class ProviderConfig(ConfigurationModel):
"email, and their account will be activated immediately upon registration."
),
)
visible = models.BooleanField(
default=False,
help_text=_(
"If this option is not selected, users will not be presented with the provider "
"as an option to authenticate with on the login screen, but manual "
"authentication using the correct link is still possible."
),
)
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
......@@ -212,6 +220,14 @@ class ProviderConfig(ConfigurationModel):
"""Gets associated Django settings.AUTHENTICATION_BACKEND string."""
return '{}.{}'.format(self.backend_class.__module__, self.backend_class.__name__)
@property
def display_for_login(self):
"""
Determines whether the provider ought to be shown as an option with
which to authenticate on the login screen, registration screen, and elsewhere.
"""
return bool(self.enabled and self.accepts_logins and self.visible)
class OAuth2ProviderConfig(ProviderConfig):
"""
......
......@@ -37,9 +37,9 @@ class Registry(object):
return sorted(cls._enabled_providers(), key=lambda provider: provider.name)
@classmethod
def accepting_logins(cls):
"""Returns list of providers that can be used to initiate logins currently"""
return [provider for provider in cls.enabled() if provider.accepts_logins]
def displayed_for_login(cls):
"""Returns list of providers that can be used to initiate logins in the UI"""
return [provider for provider in cls.enabled() if provider.display_for_login]
@classmethod
def get(cls, provider_id):
......
......@@ -11,6 +11,7 @@ class AzureADOauth2IntegrationTest(base.Oauth2IntegrationTest):
super(AzureADOauth2IntegrationTest, self).setUp()
self.provider = self.configure_azure_ad_provider(
enabled=True,
visible=True,
key='azure_ad_oauth2_key',
secret='azure_ad_oauth2_secret',
)
......
......@@ -22,7 +22,7 @@ class GenericIntegrationTest(IntegrationTestMixin, testutil.TestCase):
def setUp(self):
super(GenericIntegrationTest, self).setUp()
self.configure_dummy_provider(enabled=True)
self.configure_dummy_provider(enabled=True, visible=True)
def do_provider_login(self, provider_redirect_url):
"""
......
......@@ -19,6 +19,7 @@ class GoogleOauth2IntegrationTest(base.Oauth2IntegrationTest):
super(GoogleOauth2IntegrationTest, self).setUp()
self.provider = self.configure_google_provider(
enabled=True,
visible=True,
key='google_oauth2_key',
secret='google_oauth2_secret',
)
......
......@@ -10,6 +10,7 @@ class LinkedInOauth2IntegrationTest(base.Oauth2IntegrationTest):
super(LinkedInOauth2IntegrationTest, self).setUp()
self.provider = self.configure_linkedin_provider(
enabled=True,
visible=True,
key='linkedin_oauth2_key',
secret='linkedin_oauth2_secret',
)
......
......@@ -134,6 +134,7 @@ class TestShibIntegrationTest(IntegrationTestMixin, testutil.SAMLTestCase):
fetch_metadata = kwargs.pop('fetch_metadata', True)
kwargs.setdefault('name', self.PROVIDER_NAME)
kwargs.setdefault('enabled', True)
kwargs.setdefault('visible', True)
kwargs.setdefault('idp_slug', self.PROVIDER_IDP_SLUG)
kwargs.setdefault('entity_id', TESTSHIB_ENTITY_ID)
kwargs.setdefault('metadata_source', TESTSHIB_METADATA_URL)
......
......@@ -13,6 +13,7 @@ class TwitterIntegrationTest(base.Oauth2IntegrationTest):
super(TwitterIntegrationTest, self).setUp()
self.provider = self.configure_twitter_provider(
enabled=True,
visible=True,
key='twitter_oauth1_key',
secret='twitter_oauth1_secret',
)
......
......@@ -48,7 +48,12 @@ class RegistryTest(testutil.TestCase):
""" Test that only backend_names listed in settings.AUTHENTICATION_BACKENDS can be used """
self.configure_oauth_provider(enabled=True, name="Disallowed", backend_name="disallowed")
self.enable_saml()
self.configure_saml_provider(enabled=True, name="Disallowed", idp_slug="test", backend_name="disallowed")
self.configure_saml_provider(
enabled=True,
name="Disallowed",
idp_slug="test",
backend_name="disallowed"
)
self.assertEqual(len(provider.Registry.enabled()), 0)
def test_enabled_returns_list_of_enabled_providers_sorted_by_name(self):
......@@ -62,6 +67,23 @@ class RegistryTest(testutil.TestCase):
with patch('third_party_auth.provider._PSA_OAUTH2_BACKENDS', backend_names):
self.assertEqual(sorted(provider_names), [prov.name for prov in provider.Registry.enabled()])
def test_providers_displayed_for_login(self):
"""
Tests to ensure that only providers that we can use to log in are presented
for rendering in the UI.
"""
hidden_provider = self.configure_google_provider(visible=False, enabled=True)
normal_provider = self.configure_facebook_provider(visible=True, enabled=True)
implicitly_hidden_provider = self.configure_linkedin_provider(enabled=True)
disabled_provider = self.configure_twitter_provider(visible=True, enabled=False)
no_log_in_provider = self.configure_lti_provider()
provider_ids = [idp.provider_id for idp in provider.Registry.displayed_for_login()]
self.assertNotIn(hidden_provider.provider_id, provider_ids)
self.assertNotIn(implicitly_hidden_provider.provider_id, provider_ids)
self.assertNotIn(disabled_provider.provider_id, provider_ids)
self.assertNotIn(no_log_in_provider.provider_id, provider_ids)
self.assertIn(normal_provider.provider_id, provider_ids)
def test_get_returns_enabled_provider(self):
google_provider = self.configure_google_provider(enabled=True)
self.assertEqual(google_provider.id, provider.Registry.get(google_provider.provider_id).id)
......
......@@ -35,9 +35,9 @@ class ThirdPartyOAuthTestMixin(ThirdPartyAuthTestMixin):
UserSocialAuth.objects.create(user=self.user, provider=self.BACKEND, uid=self.social_uid)
self.oauth_client = self._create_client()
if self.BACKEND == 'google-oauth2':
self.configure_google_provider(enabled=True)
self.configure_google_provider(enabled=True, visible=True)
elif self.BACKEND == 'facebook':
self.configure_facebook_provider(enabled=True)
self.configure_facebook_provider(enabled=True, visible=True)
def _create_client(self):
"""
......
......@@ -12,7 +12,8 @@
"backend_name": "google-oauth2",
"key": "test",
"secret": "test",
"other_settings": "{}"
"other_settings": "{}",
"visible": true
}
},
{
......@@ -28,7 +29,8 @@
"backend_name": "facebook",
"key": "test",
"secret": "test",
"other_settings": "{}"
"other_settings": "{}",
"visible": true
}
},
{
......@@ -44,7 +46,8 @@
"backend_name": "dummy",
"key": "",
"secret": "",
"other_settings": "{}"
"other_settings": "{}",
"visible": true
}
}
]
......@@ -237,9 +237,10 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
super(StudentAccountLoginAndRegistrationTest, self).setUp()
# For these tests, three third party auth providers are enabled by default:
self.configure_google_provider(enabled=True)
self.configure_facebook_provider(enabled=True)
self.configure_google_provider(enabled=True, visible=True)
self.configure_facebook_provider(enabled=True, visible=True)
self.configure_dummy_provider(
visible=True,
enabled=True,
icon_class='',
icon_image=SimpleUploadedFile('icon.svg', '<svg><rect width="50" height="100"/></svg>'),
......@@ -482,8 +483,8 @@ class AccountSettingsViewTest(ThirdPartyAuthTestMixin, TestCase, ProgramsApiConf
self.request.user = self.user
# For these tests, two third party auth providers are enabled by default:
self.configure_google_provider(enabled=True)
self.configure_facebook_provider(enabled=True)
self.configure_google_provider(enabled=True, visible=True)
self.configure_facebook_provider(enabled=True, visible=True)
# Python-social saves auth failure notifcations in Django messages.
# See pipeline.get_duplicate_provider() for details.
......
......@@ -203,7 +203,7 @@ def _third_party_auth_context(request, redirect_to):
}
if third_party_auth.is_enabled():
for enabled in third_party_auth.provider.Registry.accepting_logins():
for enabled in third_party_auth.provider.Registry.displayed_for_login():
info = {
"id": enabled.provider_id,
"name": enabled.name,
......@@ -487,6 +487,8 @@ def account_settings_context(request):
# If the user is connected, sending a POST request to this url removes the connection
# information for this provider from their edX account.
'disconnect_url': pipeline.get_disconnect_url(state.provider.provider_id, state.association_id),
} for state in auth_states]
# We only want to include providers if they are either currently available to be logged
# in with, or if the user is already authenticated with them.
} for state in auth_states if state.provider.display_for_login or state.has_account]
return context
......@@ -218,7 +218,7 @@ from third_party_auth import provider, pipeline
<div class="form-actions form-third-party-auth">
% for enabled in provider.Registry.accepting_logins():
% for enabled in provider.Registry.displayed_for_login():
## Translators: provider_name is the name of an external, third-party user authentication provider (like Google or LinkedIn).
<button type="submit" class="button button-primary button-${enabled.provider_id} login-${enabled.provider_id}" onclick="thirdPartySignin(event, '${pipeline_url[enabled.provider_id]}');">
% if enabled.icon_class:
......
......@@ -24,7 +24,7 @@ from student.models import UserProfile
<div class="form-actions form-third-party-auth">
% for enabled in provider.Registry.accepting_logins():
% for enabled in provider.Registry.displayed_for_login():
## Translators: provider_name is the name of an external, third-party user authentication service (like Google or LinkedIn).
<button type="submit" class="button button-primary button-${enabled.provider_id} register-${enabled.provider_id}" onclick="thirdPartySignin(event, '${pipeline_urls[enabled.provider_id]}');">
% if enabled.icon_class:
......
......@@ -33,7 +33,7 @@ from third_party_auth import pipeline
## Translators: clicking on this removes the link between a user's edX account and their account with an external authentication provider (like Google or LinkedIn).
${_("Unlink")}
</button>
% elif state.provider.accepts_logins:
% elif state.provider.display_for_login:
<a href="${pipeline.get_login_url(state.provider.provider_id, pipeline.AUTH_ENTRY_PROFILE)}">
## Translators: clicking on this creates a link between a user's edX account and their account with an external authentication provider (like Google or LinkedIn).
${_("Link")}
......
......@@ -24,7 +24,7 @@ from student.models import UserProfile
<div class="form-actions form-third-party-auth">
% for enabled in provider.Registry.accepting_logins():
% for enabled in provider.Registry.displayed_for_login():
## Translators: provider_name is the name of an external, third-party user authentication service (like Google or LinkedIn).
<button type="submit" class="button button-primary button-${enabled.provider_id} register-${enabled.provider_id}" onclick="thirdPartySignin(event, '${pipeline_urls[enabled.provider_id]}');">
% if enabled.icon_class:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment