Merge pull request #2736 from johncox-google/johncox/feature/auth
Add auth module
This commit is contained in:
0
common/djangoapps/third_party_auth/__init__.py
Normal file
0
common/djangoapps/third_party_auth/__init__.py
Normal file
9
common/djangoapps/third_party_auth/pipeline.py
Normal file
9
common/djangoapps/third_party_auth/pipeline.py
Normal file
@@ -0,0 +1,9 @@
|
||||
"""Auth pipeline definitions."""
|
||||
|
||||
from social.pipeline import partial
|
||||
|
||||
|
||||
@partial.partial
|
||||
def step(*args, **kwargs):
|
||||
"""Fake pipeline step; just throws loudly for now."""
|
||||
raise NotImplementedError('%s, %s' % (args, kwargs))
|
||||
125
common/djangoapps/third_party_auth/provider.py
Normal file
125
common/djangoapps/third_party_auth/provider.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""Third-party auth provider definitions.
|
||||
|
||||
Loaded by Django's settings mechanism. Consequently, this module must not
|
||||
invoke the Django armature.
|
||||
"""
|
||||
|
||||
|
||||
class BaseProvider(object):
|
||||
"""Abstract base class for third-party auth providers.
|
||||
|
||||
All providers must subclass BaseProvider -- otherwise, they cannot be put
|
||||
in the provider Registry.
|
||||
"""
|
||||
|
||||
# String. Dot-delimited module.Class. The name of the backend
|
||||
# implementation to load.
|
||||
AUTHENTICATION_BACKEND = None
|
||||
# String. User-facing name of the provider. Must be unique across all
|
||||
# enabled providers.
|
||||
NAME = None
|
||||
|
||||
# Dict of string -> object. Settings that will be merged into Django's
|
||||
# settings instance. In most cases the value will be None, since real
|
||||
# values are merged from .json files (foo.auth.json; foo.env.json) onto the
|
||||
# settings instance during application initialization.
|
||||
SETTINGS = {}
|
||||
|
||||
@classmethod
|
||||
def merge_onto(cls, settings):
|
||||
"""Merge class-level settings onto a django `settings` module."""
|
||||
for key, value in cls.SETTINGS.iteritems():
|
||||
setattr(settings, key, value)
|
||||
|
||||
|
||||
class GoogleOauth2(BaseProvider):
|
||||
"""Provider for Google's Oauth2 auth system."""
|
||||
|
||||
AUTHENTICATION_BACKEND = 'social.backends.google.GoogleOAuth2'
|
||||
NAME = 'Google'
|
||||
SETTINGS = {
|
||||
'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': None,
|
||||
'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET': None,
|
||||
}
|
||||
|
||||
|
||||
class LinkedInOauth2(BaseProvider):
|
||||
"""Provider for LinkedIn's Oauth2 auth system."""
|
||||
|
||||
AUTHENTICATION_BACKEND = 'social.backends.linkedin.LinkedinOAuth2'
|
||||
NAME = 'LinkedIn'
|
||||
SETTINGS = {
|
||||
'SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY': None,
|
||||
'SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET': None,
|
||||
}
|
||||
|
||||
|
||||
class MozillaPersona(BaseProvider):
|
||||
"""Provider for Mozilla's Persona auth system."""
|
||||
|
||||
AUTHENTICATION_BACKEND = 'social.backends.persona.PersonaAuth'
|
||||
NAME = 'Mozilla Persona'
|
||||
|
||||
|
||||
class Registry(object):
|
||||
"""Singleton registry of third-party auth providers.
|
||||
|
||||
Providers must subclass BaseProvider in order to be usable in the registry.
|
||||
"""
|
||||
|
||||
_CONFIGURED = False
|
||||
_ENABLED = {}
|
||||
|
||||
@classmethod
|
||||
def _check_configured(cls):
|
||||
"""Ensures registry is configured."""
|
||||
if not cls._CONFIGURED:
|
||||
raise RuntimeError('Registry not configured')
|
||||
|
||||
@classmethod
|
||||
def _get_all(cls):
|
||||
"""Gets all provider implementations loaded into the Python runtime."""
|
||||
# BaseProvider does so have __subclassess__. pylint: disable-msg=no-member
|
||||
return {klass.NAME: klass for klass in BaseProvider.__subclasses__()}
|
||||
|
||||
@classmethod
|
||||
def _enable(cls, provider):
|
||||
"""Enables a single `provider`."""
|
||||
if provider.NAME in cls._ENABLED:
|
||||
raise ValueError('Provider %s already enabled' % provider.NAME)
|
||||
cls._ENABLED[provider.NAME] = provider
|
||||
|
||||
@classmethod
|
||||
def configure_once(cls, provider_names):
|
||||
"""Configures providers.
|
||||
|
||||
Takes `provider_names`, a list of string.
|
||||
"""
|
||||
if cls._CONFIGURED:
|
||||
raise ValueError('Provider registry already configured')
|
||||
# Flip the bit eagerly -- configure() should not be re-callable if one
|
||||
# _enable call fails.
|
||||
cls._CONFIGURED = True
|
||||
for name in provider_names:
|
||||
all_providers = cls._get_all()
|
||||
if name not in all_providers:
|
||||
raise ValueError('No implementation found for provider ' + name)
|
||||
cls._enable(all_providers.get(name))
|
||||
|
||||
@classmethod
|
||||
def enabled(cls):
|
||||
"""Returns list of enabled providers."""
|
||||
cls._check_configured()
|
||||
return sorted(cls._ENABLED.values(), key=lambda provider: provider.NAME)
|
||||
|
||||
@classmethod
|
||||
def get(cls, provider_name):
|
||||
"""Gets provider named `provider_name` string if enabled, else None."""
|
||||
cls._check_configured()
|
||||
return cls._ENABLED.get(provider_name)
|
||||
|
||||
@classmethod
|
||||
def _reset(cls):
|
||||
"""Returns the registry to an unconfigured state; for tests only."""
|
||||
cls._CONFIGURED = False
|
||||
cls._ENABLED = {}
|
||||
106
common/djangoapps/third_party_auth/settings.py
Normal file
106
common/djangoapps/third_party_auth/settings.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""Settings for the third-party auth module.
|
||||
|
||||
Defers configuration of settings so we can inspect the provider registry and
|
||||
create settings placeholders for only those values actually needed by a given
|
||||
deployment. Required by Django; consequently, this file must not invoke the
|
||||
Django armature.
|
||||
|
||||
The flow for settings registration is:
|
||||
|
||||
The base settings file contains a boolean, ENABLE_THIRD_PARTY_AUTH, indicating
|
||||
whether this module is enabled. Ancillary settings files (aws.py, dev.py) put
|
||||
options in THIRD_PARTY_SETTINGS. startup.py probes the ENABLE_THIRD_PARTY_AUTH.
|
||||
If true, it:
|
||||
|
||||
a) loads this module.
|
||||
b) calls apply_settings(), passing in settings.THIRD_PARTY_AUTH.
|
||||
THIRD_PARTY AUTH is a dict of the form
|
||||
|
||||
'THIRD_PARTY_AUTH': {
|
||||
'<PROVIDER_NAME>': {
|
||||
'<PROVIDER_SETTING_NAME>': '<PROVIDER_SETTING_VALUE>',
|
||||
[...]
|
||||
},
|
||||
[...]
|
||||
}
|
||||
|
||||
If you are using a dev settings file, your settings dict starts at the
|
||||
level of <PROVIDER_NAME> and is a map of provider name string to
|
||||
settings dict. If you are using an auth.json file, it should contain a
|
||||
THIRD_PARTY_AUTH entry as above.
|
||||
c) apply_settings() builds a list of <PROVIDER_NAMES>. These are the
|
||||
enabled third party auth providers for the deployment. These are enabled
|
||||
in provider.Registry, the canonical list of enabled providers.
|
||||
d) then, it sets global, provider-independent settings.
|
||||
e) then, it sets provider-specific settings. For each enabled provider, we
|
||||
read its SETTINGS member. These are merged onto the Django settings
|
||||
object. In most cases these are stubs and the real values are set from
|
||||
THIRD_PARTY_AUTH. All values that are set from this dict must first be
|
||||
initialized from SETTINGS. This allows us to validate the dict and
|
||||
ensure that the values match expected configuration options on the
|
||||
provider.
|
||||
f) finally, the (key, value) pairs from the dict file are merged onto the
|
||||
django settings object.
|
||||
"""
|
||||
|
||||
from . import provider
|
||||
|
||||
|
||||
def _merge_auth_info(django_settings, auth_info):
|
||||
"""Merge `auth_info` dict onto `django_settings` module."""
|
||||
enabled_provider_names = []
|
||||
to_merge = []
|
||||
|
||||
for provider_name, provider_dict in auth_info.items():
|
||||
enabled_provider_names.append(provider_name)
|
||||
# Merge iff all settings have been intialized.
|
||||
for key in provider_dict:
|
||||
if key not in dir(django_settings):
|
||||
raise ValueError('Auth setting %s not initialized' % key)
|
||||
to_merge.append(provider_dict)
|
||||
|
||||
for passed_validation in to_merge:
|
||||
for key, value in passed_validation.iteritems():
|
||||
setattr(django_settings, key, value)
|
||||
|
||||
|
||||
def _set_global_settings(django_settings):
|
||||
"""Set provider-independent settings."""
|
||||
# Register and configure python-social-auth with Django.
|
||||
django_settings.INSTALLED_APPS += (
|
||||
'social.apps.django_app.default',
|
||||
'third_party_auth',
|
||||
)
|
||||
django_settings.TEMPLATE_CONTEXT_PROCESSORS += (
|
||||
'social.apps.django_app.context_processors.backends',
|
||||
'social.apps.django_app.context_processors.login_redirect',
|
||||
)
|
||||
# Inject our customized auth pipeline. All auth backends must work with
|
||||
# this pipeline.
|
||||
django_settings.SOCIAL_AUTH_PIPELINE = (
|
||||
'third_party_auth.pipeline.step',
|
||||
)
|
||||
|
||||
|
||||
def _set_provider_settings(django_settings, enabled_providers, auth_info):
|
||||
"""Set provider-specific settings."""
|
||||
# Must prepend here so we get called first.
|
||||
django_settings.AUTHENTICATION_BACKENDS = (
|
||||
tuple(enabled_provider.AUTHENTICATION_BACKEND for enabled_provider in enabled_providers) +
|
||||
django_settings.AUTHENTICATION_BACKENDS)
|
||||
|
||||
# Merge settings from provider classes, and configure all placeholders.
|
||||
for enabled_provider in enabled_providers:
|
||||
enabled_provider.merge_onto(django_settings)
|
||||
|
||||
# Merge settings from <deployment>.auth.json.
|
||||
_merge_auth_info(django_settings, auth_info)
|
||||
|
||||
|
||||
def apply_settings(auth_info, django_settings):
|
||||
"""Apply settings from `auth_info` dict to `django_settings` module."""
|
||||
provider_names = auth_info.keys()
|
||||
provider.Registry.configure_once(provider_names)
|
||||
enabled_providers = provider.Registry.enabled()
|
||||
_set_global_settings(django_settings)
|
||||
_set_provider_settings(django_settings, enabled_providers, auth_info)
|
||||
71
common/djangoapps/third_party_auth/tests/test_provider.py
Normal file
71
common/djangoapps/third_party_auth/tests/test_provider.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Test configuration of providers.
|
||||
"""
|
||||
|
||||
from third_party_auth import provider
|
||||
from third_party_auth.tests import testutil
|
||||
|
||||
|
||||
class RegistryTest(testutil.TestCase):
|
||||
"""Tests registry discovery and operation."""
|
||||
|
||||
# Allow access to protected methods (or module-protected methods) under
|
||||
# test.
|
||||
# pylint: disable-msg=protected-access
|
||||
|
||||
def test_calling_configure_once_twice_raises_value_error(self):
|
||||
provider.Registry.configure_once([provider.GoogleOauth2.NAME])
|
||||
|
||||
with self.assertRaisesRegexp(ValueError, '^.*already configured$'):
|
||||
provider.Registry.configure_once([provider.GoogleOauth2.NAME])
|
||||
|
||||
def test_configure_once_adds_gettable_providers(self):
|
||||
provider.Registry.configure_once([provider.GoogleOauth2.NAME])
|
||||
self.assertIs(provider.GoogleOauth2, provider.Registry.get(provider.GoogleOauth2.NAME))
|
||||
|
||||
def test_configuring_provider_with_no_implementation_raises_value_error(self):
|
||||
with self.assertRaisesRegexp(ValueError, '^.*no_implementation$'):
|
||||
provider.Registry.configure_once(['no_implementation'])
|
||||
|
||||
def test_configuring_single_provider_twice_raises_value_error(self):
|
||||
provider.Registry._enable(provider.GoogleOauth2)
|
||||
|
||||
with self.assertRaisesRegexp(ValueError, '^.*already enabled'):
|
||||
provider.Registry.configure_once([provider.GoogleOauth2.NAME])
|
||||
|
||||
def test_custom_provider_can_be_enabled(self):
|
||||
name = 'CustomProvider'
|
||||
|
||||
with self.assertRaisesRegexp(ValueError, '^No implementation.*$'):
|
||||
provider.Registry.configure_once([name])
|
||||
|
||||
class CustomProvider(provider.BaseProvider):
|
||||
"""Custom class to ensure BaseProvider children outside provider can be enabled."""
|
||||
|
||||
NAME = name
|
||||
|
||||
provider.Registry._reset()
|
||||
provider.Registry.configure_once([CustomProvider.NAME])
|
||||
self.assertEqual([CustomProvider], provider.Registry.enabled())
|
||||
|
||||
def test_enabled_raises_runtime_error_if_not_configured(self):
|
||||
with self.assertRaisesRegexp(RuntimeError, '^.*not configured$'):
|
||||
provider.Registry.enabled()
|
||||
|
||||
def test_enabled_returns_list_of_enabled_providers_sorted_by_name(self):
|
||||
all_providers = provider.Registry._get_all()
|
||||
provider.Registry.configure_once(all_providers.keys())
|
||||
self.assertEqual(
|
||||
sorted(all_providers.values(), key=lambda provider: provider.NAME), provider.Registry.enabled())
|
||||
|
||||
def test_get_raises_runtime_error_if_not_configured(self):
|
||||
with self.assertRaisesRegexp(RuntimeError, '^.*not configured$'):
|
||||
provider.Registry.get('anything')
|
||||
|
||||
def test_get_returns_enabled_provider(self):
|
||||
provider.Registry.configure_once([provider.GoogleOauth2.NAME])
|
||||
self.assertIs(provider.GoogleOauth2, provider.Registry.get(provider.GoogleOauth2.NAME))
|
||||
|
||||
def test_get_returns_none_if_provider_not_enabled(self):
|
||||
provider.Registry.configure_once([])
|
||||
self.assertIsNone(provider.Registry.get(provider.MozillaPersona.NAME))
|
||||
68
common/djangoapps/third_party_auth/tests/test_settings.py
Normal file
68
common/djangoapps/third_party_auth/tests/test_settings.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Unit tests for settings code.
|
||||
"""
|
||||
|
||||
from third_party_auth import provider
|
||||
from third_party_auth import settings
|
||||
from third_party_auth.tests import testutil
|
||||
|
||||
|
||||
_ORIGINAL_AUTHENTICATION_BACKENDS = ('first_authentication_backend',)
|
||||
_ORIGINAL_INSTALLED_APPS = ('first_installed_app',)
|
||||
_ORIGINAL_TEMPLATE_CONTEXT_PROCESSORS = ('first_template_context_preprocessor',)
|
||||
_SETTINGS_MAP = {
|
||||
'AUTHENTICATION_BACKENDS': _ORIGINAL_AUTHENTICATION_BACKENDS,
|
||||
'INSTALLED_APPS': _ORIGINAL_INSTALLED_APPS,
|
||||
'TEMPLATE_CONTEXT_PROCESSORS': _ORIGINAL_TEMPLATE_CONTEXT_PROCESSORS,
|
||||
}
|
||||
|
||||
|
||||
class SettingsUnitTest(testutil.TestCase):
|
||||
"""Unit tests for settings management code."""
|
||||
|
||||
# Suppress sprurious no-member warning on fakes.
|
||||
# pylint: disable-msg=no-member
|
||||
|
||||
def setUp(self):
|
||||
super(SettingsUnitTest, self).setUp()
|
||||
self.settings = testutil.FakeDjangoSettings(_SETTINGS_MAP)
|
||||
|
||||
def test_apply_settings_adds_third_party_auth_to_installed_apps(self):
|
||||
settings.apply_settings({}, self.settings)
|
||||
self.assertIn('third_party_auth', self.settings.INSTALLED_APPS)
|
||||
|
||||
def test_apply_settings_enables_no_providers_and_completes_when_app_info_empty(self):
|
||||
settings.apply_settings({}, self.settings)
|
||||
self.assertEqual([], provider.Registry.enabled())
|
||||
|
||||
def test_apply_settings_initializes_stubs_and_merges_settings_from_auth_info(self):
|
||||
for key in provider.GoogleOauth2.SETTINGS:
|
||||
self.assertFalse(hasattr(self.settings, key))
|
||||
|
||||
auth_info = {
|
||||
provider.GoogleOauth2.NAME: {
|
||||
'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': 'google_oauth2_key',
|
||||
},
|
||||
}
|
||||
settings.apply_settings(auth_info, self.settings)
|
||||
self.assertEqual('google_oauth2_key', self.settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY)
|
||||
self.assertIsNone(self.settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET)
|
||||
|
||||
def test_apply_settings_prepends_auth_backends(self):
|
||||
self.assertEqual(_ORIGINAL_AUTHENTICATION_BACKENDS, self.settings.AUTHENTICATION_BACKENDS)
|
||||
settings.apply_settings({provider.GoogleOauth2.NAME: {}, provider.MozillaPersona.NAME: {}}, self.settings)
|
||||
self.assertEqual((
|
||||
provider.GoogleOauth2.AUTHENTICATION_BACKEND, provider.MozillaPersona.AUTHENTICATION_BACKEND) +
|
||||
_ORIGINAL_AUTHENTICATION_BACKENDS,
|
||||
self.settings.AUTHENTICATION_BACKENDS)
|
||||
|
||||
def test_apply_settings_raises_value_error_if_provider_contains_uninitialized_setting(self):
|
||||
bad_setting_name = 'bad_setting'
|
||||
self.assertNotIn('bad_setting_name', provider.GoogleOauth2.SETTINGS)
|
||||
auth_info = {
|
||||
provider.GoogleOauth2.NAME: {
|
||||
bad_setting_name: None,
|
||||
},
|
||||
}
|
||||
with self.assertRaisesRegexp(ValueError, '^.*not initialized$'):
|
||||
settings.apply_settings(auth_info, self.settings)
|
||||
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
Integration tests for settings code.
|
||||
"""
|
||||
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from third_party_auth import provider
|
||||
from third_party_auth import settings as auth_settings
|
||||
from third_party_auth.tests import testutil
|
||||
|
||||
_AUTH_FEATURES_KEY = 'ENABLE_THIRD_PARTY_AUTH'
|
||||
|
||||
|
||||
class SettingsIntegrationTest(testutil.TestCase):
|
||||
"""Integration tests of auth settings pipeline."""
|
||||
|
||||
@unittest.skipUnless(_AUTH_FEATURES_KEY in settings.FEATURES, _AUTH_FEATURES_KEY + ' not in settings.FEATURES')
|
||||
def test_enable_third_party_auth_is_disabled_by_default(self):
|
||||
self.assertIs(False, settings.FEATURES.get(_AUTH_FEATURES_KEY))
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_THIRD_PARTY_AUTH': True})
|
||||
def test_can_enable_google_oauth2(self):
|
||||
auth_settings.apply_settings({'Google': {'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': 'google_key'}}, settings)
|
||||
self.assertEqual([provider.GoogleOauth2], provider.Registry.enabled())
|
||||
self.assertEqual('google_key', settings.SOCIAL_AUTH_GOOGLE_OAUTH2_KEY)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_THIRD_PARTY_AUTH': True})
|
||||
def test_can_enable_linkedin_oauth2(self):
|
||||
auth_settings.apply_settings({'LinkedIn': {'SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY': 'linkedin_key'}}, settings)
|
||||
self.assertEqual([provider.LinkedInOauth2], provider.Registry.enabled())
|
||||
self.assertEqual('linkedin_key', settings.SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY)
|
||||
|
||||
@mock.patch.dict(settings.FEATURES, {'ENABLE_THIRD_PARTY_ATUH': True})
|
||||
def test_can_enable_mozilla_persona(self):
|
||||
auth_settings.apply_settings({'Mozilla Persona': {}}, settings)
|
||||
self.assertEqual([provider.MozillaPersona], provider.Registry.enabled())
|
||||
34
common/djangoapps/third_party_auth/tests/testutil.py
Normal file
34
common/djangoapps/third_party_auth/tests/testutil.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""
|
||||
Utilities for writing third_party_auth tests.
|
||||
|
||||
Used by Django and non-Django tests; must not have Django deps.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
|
||||
from third_party_auth import provider
|
||||
|
||||
|
||||
class FakeDjangoSettings(object):
|
||||
"""A fake for Django settings."""
|
||||
|
||||
def __init__(self, mappings):
|
||||
"""Initializes the fake from `mappings`, a dict."""
|
||||
for key, value in mappings.iteritems():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
"""Base class for auth test cases."""
|
||||
|
||||
# Allow access to protected methods (or module-protected methods) under
|
||||
# test.
|
||||
# pylint: disable-msg=protected-access
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
provider.Registry._reset()
|
||||
|
||||
def tearDown(self):
|
||||
provider.Registry._reset()
|
||||
super(TestCase, self).tearDown()
|
||||
7
common/djangoapps/third_party_auth/urls.py
Normal file
7
common/djangoapps/third_party_auth/urls.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Url configuration for the auth module."""
|
||||
|
||||
from django.conf.urls import include, patterns, url
|
||||
|
||||
urlpatterns = patterns(
|
||||
'', url(r'^auth/', include('social.apps.django_app.urls', namespace='social')),
|
||||
)
|
||||
@@ -385,3 +385,6 @@ TIME_ZONE_DISPLAYED_FOR_DEADLINES = ENV_TOKENS.get("TIME_ZONE_DISPLAYED_FOR_DEAD
|
||||
|
||||
##### X-Frame-Options response header settings #####
|
||||
X_FRAME_OPTIONS = ENV_TOKENS.get('X_FRAME_OPTIONS', X_FRAME_OPTIONS)
|
||||
|
||||
##### Third-party auth options ################################################
|
||||
THIRD_PARTY_AUTH = AUTH_TOKENS.get('THIRD_PARTY_AUTH', THIRD_PARTY_AUTH)
|
||||
|
||||
@@ -234,6 +234,10 @@ FEATURES = {
|
||||
|
||||
# Turn on/off Microsites feature
|
||||
'USE_MICROSITES': False,
|
||||
|
||||
# Turn on third-party auth. Disabled for now because full implementations are not yet available. Remember to syncdb
|
||||
# if you enable this; we don't create tables by default.
|
||||
'ENABLE_THIRD_PARTY_AUTH': False,
|
||||
}
|
||||
|
||||
# Used for A/B testing
|
||||
@@ -1247,6 +1251,7 @@ LINKEDIN_API = {
|
||||
'COMPANY_ID': '2746406',
|
||||
}
|
||||
|
||||
|
||||
##### ACCOUNT LOCKOUT DEFAULT PARAMETERS #####
|
||||
MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED = 5
|
||||
MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = 15 * 60
|
||||
@@ -1465,3 +1470,7 @@ for app_name in OPTIONAL_APPS:
|
||||
except ImportError:
|
||||
continue
|
||||
INSTALLED_APPS += (app_name,)
|
||||
|
||||
# Stub for third_party_auth options.
|
||||
# See common/djangoapps/third_party_auth/settings.py for configuration details.
|
||||
THIRD_PARTY_AUTH = {}
|
||||
|
||||
@@ -26,6 +26,9 @@ def run():
|
||||
if settings.FEATURES.get('USE_MICROSITES', False):
|
||||
enable_microsites()
|
||||
|
||||
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
|
||||
enable_third_party_auth()
|
||||
|
||||
|
||||
def enable_theme():
|
||||
"""
|
||||
@@ -99,3 +102,14 @@ def enable_microsites():
|
||||
edxmako.startup.run()
|
||||
|
||||
settings.STATICFILES_DIRS.insert(0, microsites_root)
|
||||
|
||||
|
||||
def enable_third_party_auth():
|
||||
"""
|
||||
Enable the use of third_party_auth, which allows users to sign in to edX
|
||||
using other identity providers. For configuration details, see
|
||||
common/djangoapps/third_party_auth/settings.py.
|
||||
"""
|
||||
|
||||
from third_party_auth import settings as auth_settings
|
||||
auth_settings.apply_settings(settings.THIRD_PARTY_AUTH, settings)
|
||||
|
||||
@@ -488,6 +488,12 @@ if settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING'):
|
||||
url(r'^auto_auth$', 'student.views.auto_auth'),
|
||||
)
|
||||
|
||||
# Third-party auth.
|
||||
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
|
||||
urlpatterns += (
|
||||
url(r'', include('third_party_auth.urls')),
|
||||
)
|
||||
|
||||
urlpatterns = patterns(*urlpatterns)
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
4
pylintrc
4
pylintrc
@@ -153,10 +153,10 @@ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns)$
|
||||
class-rgx=[A-Z_][a-zA-Z0-9]+$
|
||||
|
||||
# Regular expression which should only match correct function names
|
||||
function-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
function-rgx=([a-z_][a-z0-9_]{2,30}|test_[a-z0-9_]+)$
|
||||
|
||||
# Regular expression which should only match correct method names
|
||||
method-rgx=([a-z_][a-z0-9_]{2,60}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff)$
|
||||
method-rgx=([a-z_][a-z0-9_]{2,60}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$
|
||||
|
||||
# Regular expression which should only match correct instance attribute names
|
||||
attr-rgx=[a-z_][a-z0-9_]{2,30}$
|
||||
|
||||
@@ -60,6 +60,7 @@ pyparsing==1.5.6
|
||||
python-memcached==1.48
|
||||
python-openid==2.2.5
|
||||
python-dateutil==2.1
|
||||
python-social-auth==0.1.21
|
||||
pytz==2012h
|
||||
pysrt==0.4.7
|
||||
PyYAML==3.10
|
||||
|
||||
Reference in New Issue
Block a user