Merge pull request #17276 from edx/bexline/configure_saml_certs
ENT-839 Add ability to configure SP metadata for an IdP
This commit is contained in:
@@ -73,7 +73,7 @@ class SAMLProviderConfigAdmin(KeyedConfigurationModelAdmin):
|
||||
""" Don't show every single field in the admin change list """
|
||||
return (
|
||||
'name_with_update_link', 'enabled', 'site', 'entity_id', 'metadata_source',
|
||||
'has_data', 'mode', 'change_date', 'changed_by',
|
||||
'has_data', 'mode', 'saml_configuration', 'change_date', 'changed_by',
|
||||
)
|
||||
|
||||
list_display_links = None
|
||||
@@ -142,7 +142,7 @@ class SAMLConfigurationAdmin(KeyedConfigurationModelAdmin):
|
||||
def get_list_display(self, request):
|
||||
""" Shorten the public/private keys in the change view """
|
||||
return (
|
||||
'site', 'change_date', 'changed_by', 'enabled', 'entity_id',
|
||||
'site', 'slug', 'change_date', 'changed_by', 'enabled', 'entity_id',
|
||||
'org_info_str', 'key_summary', 'edit_link',
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('third_party_auth', '0015_samlproviderconfig_archived'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='samlconfiguration',
|
||||
name='slug',
|
||||
field=models.SlugField(default=b'default', help_text=b'A short string uniquely identifying this configuration. Cannot contain spaces. Examples: "ubc", "mit-staging"', max_length=30),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='samlproviderconfig',
|
||||
name='saml_configuration',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='third_party_auth.SAMLConfiguration', null=True),
|
||||
),
|
||||
]
|
||||
@@ -359,6 +359,149 @@ class OAuth2ProviderConfig(ProviderConfig):
|
||||
raise KeyError
|
||||
|
||||
|
||||
class SAMLConfiguration(ConfigurationModel):
|
||||
"""
|
||||
General configuration required for this edX instance to act as a SAML
|
||||
Service Provider and allow users to authenticate via third party SAML
|
||||
Identity Providers (IdPs)
|
||||
"""
|
||||
KEY_FIELDS = ('site_id', 'slug')
|
||||
site = models.ForeignKey(
|
||||
Site,
|
||||
default=settings.SITE_ID,
|
||||
related_name='%(class)ss',
|
||||
help_text=_(
|
||||
'The Site that this SAML configuration belongs to.'
|
||||
),
|
||||
)
|
||||
slug = models.SlugField(
|
||||
max_length=30,
|
||||
default='default',
|
||||
help_text=(
|
||||
'A short string uniquely identifying this configuration. '
|
||||
'Cannot contain spaces. Examples: "ubc", "mit-staging"'
|
||||
),
|
||||
)
|
||||
private_key = models.TextField(
|
||||
help_text=(
|
||||
'To generate a key pair as two files, run '
|
||||
'"openssl req -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.key". '
|
||||
'Paste the contents of saml.key here. '
|
||||
'For increased security, you can avoid storing this in your database by leaving '
|
||||
'this field blank and setting it via the SOCIAL_AUTH_SAML_SP_PRIVATE_KEY setting '
|
||||
'in your instance\'s Django settings (or lms.auth.json).'
|
||||
),
|
||||
blank=True,
|
||||
)
|
||||
public_key = models.TextField(
|
||||
help_text=(
|
||||
'Public key certificate. '
|
||||
'For increased security, you can avoid storing this in your database by leaving '
|
||||
'this field blank and setting it via the SOCIAL_AUTH_SAML_SP_PUBLIC_CERT setting '
|
||||
'in your instance\'s Django settings (or lms.auth.json).'
|
||||
),
|
||||
blank=True,
|
||||
)
|
||||
entity_id = models.CharField(max_length=255, default="http://saml.example.com", verbose_name="Entity ID")
|
||||
org_info_str = models.TextField(
|
||||
verbose_name="Organization Info",
|
||||
default='{"en-US": {"url": "http://www.example.com", "displayname": "Example Inc.", "name": "example"}}',
|
||||
help_text="JSON dictionary of 'url', 'displayname', and 'name' for each language",
|
||||
)
|
||||
other_config_str = models.TextField(
|
||||
default='{\n"SECURITY_CONFIG": {"metadataCacheDuration": 604800, "signMetadata": false}\n}',
|
||||
help_text=(
|
||||
"JSON object defining advanced settings that are passed on to python-saml. "
|
||||
"Valid keys that can be set here include: SECURITY_CONFIG and SP_EXTRA"
|
||||
),
|
||||
)
|
||||
|
||||
class Meta(object):
|
||||
app_label = "third_party_auth"
|
||||
verbose_name = "SAML Configuration"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return human-readable string representation.
|
||||
"""
|
||||
return "SAMLConfiguration {site}: {slug} on {date:%Y-%m-%d %H:%M:%S}".format(
|
||||
site=self.site.name,
|
||||
slug=self.slug,
|
||||
date=self.change_date,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
""" Standardize and validate fields """
|
||||
super(SAMLConfiguration, self).clean()
|
||||
self.org_info_str = clean_json(self.org_info_str, dict)
|
||||
self.other_config_str = clean_json(self.other_config_str, dict)
|
||||
|
||||
self.private_key = (
|
||||
self.private_key
|
||||
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
|
||||
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||
.replace("-----END RSA PRIVATE KEY-----", "")
|
||||
.replace("-----END PRIVATE KEY-----", "")
|
||||
.strip()
|
||||
)
|
||||
self.public_key = (
|
||||
self.public_key
|
||||
.replace("-----BEGIN CERTIFICATE-----", "")
|
||||
.replace("-----END CERTIFICATE-----", "")
|
||||
.strip()
|
||||
)
|
||||
|
||||
def get_setting(self, name):
|
||||
""" Get the value of a setting, or raise KeyError """
|
||||
default_saml_contact = {
|
||||
# Default contact information to put into the SAML metadata that gets generated by python-saml.
|
||||
"givenName": _("{platform_name} Support").format(
|
||||
platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
|
||||
),
|
||||
"emailAddress": configuration_helpers.get_value('TECH_SUPPORT_EMAIL', settings.TECH_SUPPORT_EMAIL),
|
||||
}
|
||||
|
||||
if name == "ORG_INFO":
|
||||
return json.loads(self.org_info_str)
|
||||
if name == "SP_ENTITY_ID":
|
||||
return self.entity_id
|
||||
if name == "SP_PUBLIC_CERT":
|
||||
if self.public_key:
|
||||
return self.public_key
|
||||
# To allow instances to avoid storing keys in the DB, the key pair can also be set via Django:
|
||||
if self.slug == 'default':
|
||||
return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '')
|
||||
else:
|
||||
public_certs = getattr(settings, 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT_DICT', {})
|
||||
return public_certs.get(self.slug, '')
|
||||
if name == "SP_PRIVATE_KEY":
|
||||
if self.private_key:
|
||||
return self.private_key
|
||||
# To allow instances to avoid storing keys in the DB, the private key can also be set via Django:
|
||||
if self.slug == 'default':
|
||||
return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '')
|
||||
else:
|
||||
private_keys = getattr(settings, 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY_DICT', {})
|
||||
return private_keys.get(self.slug, '')
|
||||
other_config = {
|
||||
# These defaults can be overriden by self.other_config_str
|
||||
"GET_ALL_EXTRA_DATA": True, # Save all attribute values the IdP sends into the UserSocialAuth table
|
||||
"TECHNICAL_CONTACT": default_saml_contact,
|
||||
"SUPPORT_CONTACT": default_saml_contact,
|
||||
}
|
||||
other_config.update(json.loads(self.other_config_str))
|
||||
return other_config[name] # SECURITY_CONFIG, SP_EXTRA, or similar extra settings
|
||||
|
||||
|
||||
def active_saml_configurations_filter():
|
||||
"""
|
||||
Returns a mapping to be used for the SAMLProviderConfig to limit the SAMLConfiguration choices to the current set.
|
||||
"""
|
||||
query_set = SAMLConfiguration.objects.current_set()
|
||||
return {'id__in': query_set.values_list('id', flat=True)}
|
||||
|
||||
|
||||
class SAMLProviderConfig(ProviderConfig):
|
||||
"""
|
||||
Configuration Entry for a SAML/Shibboleth provider.
|
||||
@@ -402,7 +545,9 @@ class SAMLProviderConfig(ProviderConfig):
|
||||
help_text="URN of SAML attribute containing the user's email address[es]. Leave blank for default.")
|
||||
automatic_refresh_enabled = models.BooleanField(
|
||||
default=True, verbose_name="Enable automatic metadata refresh",
|
||||
help_text="When checked, the SAML provider's metadata will be included in the automatic refresh job, if configured.")
|
||||
help_text="When checked, the SAML provider's metadata will be included "
|
||||
"in the automatic refresh job, if configured."
|
||||
)
|
||||
identity_provider_type = models.CharField(
|
||||
max_length=128, blank=False, verbose_name="Identity Provider Type", default=STANDARD_SAML_PROVIDER_KEY,
|
||||
choices=get_saml_idp_choices(), help_text=(
|
||||
@@ -431,6 +576,13 @@ class SAMLProviderConfig(ProviderConfig):
|
||||
'in this field for additional configuration.'
|
||||
))
|
||||
archived = models.BooleanField(default=False)
|
||||
saml_configuration = models.ForeignKey(
|
||||
SAMLConfiguration,
|
||||
on_delete=models.SET_NULL,
|
||||
limit_choices_to=active_saml_configurations_filter,
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
""" Standardize and validate fields """
|
||||
@@ -494,119 +646,16 @@ class SAMLProviderConfig(ProviderConfig):
|
||||
raise AuthNotConfigured(provider_name=self.name)
|
||||
conf['x509cert'] = data.public_key
|
||||
conf['url'] = data.sso_url
|
||||
|
||||
# Add SAMLConfiguration appropriate for this IdP
|
||||
conf['saml_sp_configuration'] = (
|
||||
self.saml_configuration or
|
||||
SAMLConfiguration.current(self.site.id, 'default')
|
||||
)
|
||||
idp_class = get_saml_idp_class(self.identity_provider_type)
|
||||
return idp_class(self.idp_slug, **conf)
|
||||
|
||||
|
||||
class SAMLConfiguration(ConfigurationModel):
|
||||
"""
|
||||
General configuration required for this edX instance to act as a SAML
|
||||
Service Provider and allow users to authenticate via third party SAML
|
||||
Identity Providers (IdPs)
|
||||
"""
|
||||
KEY_FIELDS = ('site_id', )
|
||||
site = models.ForeignKey(
|
||||
Site,
|
||||
default=settings.SITE_ID,
|
||||
related_name='%(class)ss',
|
||||
help_text=_(
|
||||
'The Site that this SAML configuration belongs to.'
|
||||
),
|
||||
)
|
||||
private_key = models.TextField(
|
||||
help_text=(
|
||||
'To generate a key pair as two files, run '
|
||||
'"openssl req -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.key". '
|
||||
'Paste the contents of saml.key here. '
|
||||
'For increased security, you can avoid storing this in your database by leaving '
|
||||
'this field blank and setting it via the SOCIAL_AUTH_SAML_SP_PRIVATE_KEY setting '
|
||||
'in your instance\'s Django settings (or lms.auth.json).'
|
||||
),
|
||||
blank=True,
|
||||
)
|
||||
public_key = models.TextField(
|
||||
help_text=(
|
||||
'Public key certificate. '
|
||||
'For increased security, you can avoid storing this in your database by leaving '
|
||||
'this field blank and setting it via the SOCIAL_AUTH_SAML_SP_PUBLIC_CERT setting '
|
||||
'in your instance\'s Django settings (or lms.auth.json).'
|
||||
),
|
||||
blank=True,
|
||||
)
|
||||
entity_id = models.CharField(max_length=255, default="http://saml.example.com", verbose_name="Entity ID")
|
||||
org_info_str = models.TextField(
|
||||
verbose_name="Organization Info",
|
||||
default='{"en-US": {"url": "http://www.example.com", "displayname": "Example Inc.", "name": "example"}}',
|
||||
help_text="JSON dictionary of 'url', 'displayname', and 'name' for each language",
|
||||
)
|
||||
other_config_str = models.TextField(
|
||||
default='{\n"SECURITY_CONFIG": {"metadataCacheDuration": 604800, "signMetadata": false}\n}',
|
||||
help_text=(
|
||||
"JSON object defining advanced settings that are passed on to python-saml. "
|
||||
"Valid keys that can be set here include: SECURITY_CONFIG and SP_EXTRA"
|
||||
),
|
||||
)
|
||||
|
||||
class Meta(object):
|
||||
app_label = "third_party_auth"
|
||||
verbose_name = "SAML Configuration"
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def clean(self):
|
||||
""" Standardize and validate fields """
|
||||
super(SAMLConfiguration, self).clean()
|
||||
self.org_info_str = clean_json(self.org_info_str, dict)
|
||||
self.other_config_str = clean_json(self.other_config_str, dict)
|
||||
|
||||
self.private_key = (
|
||||
self.private_key
|
||||
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
|
||||
.replace("-----BEGIN PRIVATE KEY-----", "")
|
||||
.replace("-----END RSA PRIVATE KEY-----", "")
|
||||
.replace("-----END PRIVATE KEY-----", "")
|
||||
.strip()
|
||||
)
|
||||
self.public_key = (
|
||||
self.public_key
|
||||
.replace("-----BEGIN CERTIFICATE-----", "")
|
||||
.replace("-----END CERTIFICATE-----", "")
|
||||
.strip()
|
||||
)
|
||||
|
||||
def get_setting(self, name):
|
||||
""" Get the value of a setting, or raise KeyError """
|
||||
default_saml_contact = {
|
||||
# Default contact information to put into the SAML metadata that gets generated by python-saml.
|
||||
"givenName": _("{platform_name} Support").format(
|
||||
platform_name=configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
|
||||
),
|
||||
"emailAddress": configuration_helpers.get_value('TECH_SUPPORT_EMAIL', settings.TECH_SUPPORT_EMAIL),
|
||||
}
|
||||
|
||||
if name == "ORG_INFO":
|
||||
return json.loads(self.org_info_str)
|
||||
if name == "SP_ENTITY_ID":
|
||||
return self.entity_id
|
||||
if name == "SP_PUBLIC_CERT":
|
||||
if self.public_key:
|
||||
return self.public_key
|
||||
# To allow instances to avoid storing keys in the DB, the key pair can also be set via Django:
|
||||
return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '')
|
||||
if name == "SP_PRIVATE_KEY":
|
||||
if self.private_key:
|
||||
return self.private_key
|
||||
# To allow instances to avoid storing keys in the DB, the private key can also be set via Django:
|
||||
return getattr(settings, 'SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '')
|
||||
other_config = {
|
||||
# These defaults can be overriden by self.other_config_str
|
||||
"GET_ALL_EXTRA_DATA": True, # Save all attribute values the IdP sends into the UserSocialAuth table
|
||||
"TECHNICAL_CONTACT": default_saml_contact,
|
||||
"SUPPORT_CONTACT": default_saml_contact,
|
||||
}
|
||||
other_config.update(json.loads(self.other_config_str))
|
||||
return other_config[name] # SECURITY_CONFIG, SP_EXTRA, or similar extra settings
|
||||
|
||||
|
||||
class SAMLProviderData(models.Model):
|
||||
"""
|
||||
Data about a SAML IdP that is fetched automatically by 'manage.py saml pull'
|
||||
|
||||
@@ -33,7 +33,7 @@ class Registry(object):
|
||||
provider = OAuth2ProviderConfig.current(oauth2_slug)
|
||||
if provider.enabled_for_current_site and provider.backend_name in _PSA_OAUTH2_BACKENDS:
|
||||
yield provider
|
||||
if SAMLConfiguration.is_enabled(Site.objects.get_current(get_current_request())):
|
||||
if SAMLConfiguration.is_enabled(Site.objects.get_current(get_current_request()), 'default'):
|
||||
idp_slugs = SAMLProviderConfig.key_values('idp_slug', flat=True)
|
||||
for idp_slug in idp_slugs:
|
||||
provider = SAMLProviderConfig.current(idp_slug)
|
||||
@@ -118,7 +118,7 @@ class Registry(object):
|
||||
if provider.backend_name == backend_name and provider.enabled_for_current_site:
|
||||
yield provider
|
||||
elif backend_name in _PSA_SAML_BACKENDS and SAMLConfiguration.is_enabled(
|
||||
Site.objects.get_current(get_current_request())):
|
||||
Site.objects.get_current(get_current_request()), 'default'):
|
||||
idp_names = SAMLProviderConfig.key_values('idp_slug', flat=True)
|
||||
for idp_name in idp_names:
|
||||
provider = SAMLProviderConfig.current(idp_name)
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.contrib.sites.models import Site
|
||||
from django.http import Http404
|
||||
from django.utils.functional import cached_property
|
||||
from django_countries import countries
|
||||
from onelogin.saml2.settings import OneLogin_Saml2_Settings
|
||||
from social_core.backends.saml import OID_EDU_PERSON_ENTITLEMENT, SAMLAuth, SAMLIdentityProvider
|
||||
from social_core.exceptions import AuthForbidden
|
||||
|
||||
@@ -38,6 +39,62 @@ class SAMLAuthBackend(SAMLAuth): # pylint: disable=abstract-method
|
||||
except KeyError:
|
||||
return self.strategy.setting(name, default, backend=self)
|
||||
|
||||
def get_idp_setting(self, idp, name, default=None):
|
||||
try:
|
||||
return idp.saml_sp_configuration.get_setting(name)
|
||||
except KeyError:
|
||||
return self.setting(name, default)
|
||||
|
||||
def generate_saml_config(self, idp=None):
|
||||
"""
|
||||
Override of SAMLAuth.generate_saml_config to use an idp's configured saml_sp_configuration if given.
|
||||
"""
|
||||
if idp:
|
||||
abs_completion_url = self.redirect_uri
|
||||
config = {
|
||||
'contactPerson': {
|
||||
'technical': self.get_idp_setting(idp, 'TECHNICAL_CONTACT'),
|
||||
'support': self.get_idp_setting(idp, 'SUPPORT_CONTACT')
|
||||
},
|
||||
'debug': True,
|
||||
'idp': idp.saml_config_dict if idp else {},
|
||||
'organization': self.get_idp_setting(idp, 'ORG_INFO'),
|
||||
'security': {
|
||||
'metadataValidUntil': '',
|
||||
'metadataCacheDuration': 'P10D', # metadata valid for ten days
|
||||
},
|
||||
'sp': {
|
||||
'assertionConsumerService': {
|
||||
'url': abs_completion_url,
|
||||
# python-saml only supports HTTP-POST
|
||||
'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
|
||||
},
|
||||
'entityId': self.get_idp_setting(idp, 'SP_ENTITY_ID'),
|
||||
'x509cert': self.get_idp_setting(idp, 'SP_PUBLIC_CERT'),
|
||||
'privateKey': self.get_idp_setting(idp, 'SP_PRIVATE_KEY'),
|
||||
},
|
||||
'strict': True, # We must force strict mode - for security
|
||||
}
|
||||
config["security"].update(self.get_idp_setting(idp, "SECURITY_CONFIG", {}))
|
||||
config["sp"].update(self.get_idp_setting(idp, "SP_EXTRA", {}))
|
||||
return config
|
||||
else:
|
||||
return super(SAMLAuthBackend, self).generate_saml_config()
|
||||
|
||||
def generate_metadata_xml(self, idp_name=None): # pylint: disable=arguments-differ
|
||||
"""
|
||||
Override of SAMLAuth.generate_metadata_xml to accept an optional idp parameter.
|
||||
"""
|
||||
idp = self.get_idp(idp_name) if idp_name else None
|
||||
config = self.generate_saml_config(idp)
|
||||
saml_settings = OneLogin_Saml2_Settings(
|
||||
config,
|
||||
sp_validation_only=True
|
||||
)
|
||||
metadata = saml_settings.get_sp_metadata()
|
||||
errors = saml_settings.validate_metadata(metadata)
|
||||
return metadata, errors
|
||||
|
||||
def auth_url(self):
|
||||
"""
|
||||
Check that SAML is enabled and that the request includes an 'idp'
|
||||
@@ -98,7 +155,7 @@ class SAMLAuthBackend(SAMLAuth): # pylint: disable=abstract-method
|
||||
@cached_property
|
||||
def _config(self):
|
||||
from .models import SAMLConfiguration
|
||||
return SAMLConfiguration.current(Site.objects.get_current(get_current_request()))
|
||||
return SAMLConfiguration.current(Site.objects.get_current(get_current_request()), 'default')
|
||||
|
||||
|
||||
class EdXSAMLIdentityProvider(SAMLIdentityProvider):
|
||||
@@ -120,6 +177,11 @@ class EdXSAMLIdentityProvider(SAMLIdentityProvider):
|
||||
})
|
||||
return details
|
||||
|
||||
@property
|
||||
def saml_sp_configuration(self):
|
||||
"""Get the SAMLConfiguration for this IdP"""
|
||||
return self.conf['saml_sp_configuration']
|
||||
|
||||
|
||||
class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
|
||||
"""
|
||||
|
||||
@@ -52,12 +52,13 @@ def fetch_saml_metadata():
|
||||
url_map = {}
|
||||
for idp_slug in saml_providers:
|
||||
config = SAMLProviderConfig.current(idp_slug)
|
||||
saml_config_slug = config.saml_configuration.slug if config.saml_configuration else 'default'
|
||||
|
||||
# Skip SAML provider configurations which do not qualify for fetching
|
||||
if any([
|
||||
not config.enabled,
|
||||
not config.automatic_refresh_enabled,
|
||||
not SAMLConfiguration.is_enabled(config.site)
|
||||
not SAMLConfiguration.is_enabled(config.site, saml_config_slug)
|
||||
]):
|
||||
num_skipped += 1
|
||||
continue
|
||||
|
||||
@@ -86,7 +86,7 @@ class ThirdPartyAuthTestMixin(object):
|
||||
def configure_saml_provider(self, **kwargs):
|
||||
""" Update the settings for a SAML-based third party auth provider """
|
||||
self.assertTrue(
|
||||
SAMLConfiguration.is_enabled(Site.objects.get_current()),
|
||||
SAMLConfiguration.is_enabled(Site.objects.get_current(), 'default'),
|
||||
"SAML Provider Configuration only works if SAML is enabled."
|
||||
)
|
||||
obj = SAMLProviderConfig(**kwargs)
|
||||
|
||||
@@ -13,7 +13,7 @@ from social_core.utils import setting_name
|
||||
from student.models import UserProfile
|
||||
from student.views import compose_and_send_activation_email
|
||||
|
||||
from .models import SAMLConfiguration
|
||||
from .models import SAMLConfiguration, SAMLProviderConfig
|
||||
|
||||
URL_NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'social'
|
||||
|
||||
@@ -41,13 +41,19 @@ def saml_metadata_view(request):
|
||||
Get the Service Provider metadata for this edx-platform instance.
|
||||
You must send this XML to any Shibboleth Identity Provider that you wish to use.
|
||||
"""
|
||||
if not SAMLConfiguration.is_enabled(request.site):
|
||||
idp_slug = request.GET.get('tpa_hint', None)
|
||||
saml_config = 'default'
|
||||
if idp_slug:
|
||||
idp = SAMLProviderConfig.current(idp_slug)
|
||||
if idp.saml_configuration:
|
||||
saml_config = idp.saml_configuration.slug
|
||||
if not SAMLConfiguration.is_enabled(request.site, saml_config):
|
||||
raise Http404
|
||||
complete_url = reverse('social:complete', args=("tpa-saml", ))
|
||||
if settings.APPEND_SLASH and not complete_url.endswith('/'):
|
||||
complete_url = complete_url + '/' # Required for consistency
|
||||
saml_backend = load_backend(load_strategy(request), "tpa-saml", redirect_uri=complete_url)
|
||||
metadata, errors = saml_backend.generate_metadata_xml()
|
||||
metadata, errors = saml_backend.generate_metadata_xml(idp_slug)
|
||||
|
||||
if not errors:
|
||||
return HttpResponse(content=metadata, content_type='text/xml')
|
||||
|
||||
@@ -711,6 +711,8 @@ if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
|
||||
# if you want (though it's easier to format the key values as JSON without the delimiters).
|
||||
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = AUTH_TOKENS.get('SOCIAL_AUTH_SAML_SP_PRIVATE_KEY', '')
|
||||
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = AUTH_TOKENS.get('SOCIAL_AUTH_SAML_SP_PUBLIC_CERT', '')
|
||||
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY_DICT = AUTH_TOKENS.get('SOCIAL_AUTH_SAML_SP_PRIVATE_KEY_DICT', {})
|
||||
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT_DICT = AUTH_TOKENS.get('SOCIAL_AUTH_SAML_SP_PUBLIC_CERT_DICT', {})
|
||||
SOCIAL_AUTH_OAUTH_SECRETS = AUTH_TOKENS.get('SOCIAL_AUTH_OAUTH_SECRETS', {})
|
||||
SOCIAL_AUTH_LTI_CONSUMER_SECRETS = AUTH_TOKENS.get('SOCIAL_AUTH_LTI_CONSUMER_SECRETS', {})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user