feat: configure SP certificate and private key via SAMLConfiguration (#1531)

This commit is contained in:
Markos Gogoulos
2026-05-31 16:16:46 +03:00
committed by GitHub
parent a3fe375a83
commit 95644dc961
7 changed files with 46 additions and 12 deletions
+1 -1
View File
@@ -1 +1 @@
VERSION = "8.1.2" VERSION = "8.2.0"
+2
View File
@@ -947,6 +947,8 @@ Select the SAML Configurations tab, create a new one and set:
3. **SSO URL**: 3. **SSO URL**:
4. **SLO URL**: 4. **SLO URL**:
5. **SP Metadata URL**: The metadata URL that the IDP will utilize. This can be https://{portal}/saml/metadata and is autogenerated by MediaCMS 5. **SP Metadata URL**: The metadata URL that the IDP will utilize. This can be https://{portal}/saml/metadata and is autogenerated by MediaCMS
6. **SP Certificate** (optional): SP x509 certificate (PEM). Enables encrypted/signed SAML communication. If set, the SP Private Key must also be provided, and the certificate is published in the SP metadata so the IDP can encrypt assertions to MediaCMS.
7. **SP Private Key** (optional): SP private key (PEM). Used to sign AuthnRequests/LogoutRequests and to decrypt assertions encrypted by the IDP. Required if SP Certificate is provided.
- Step 3: Set other Options - Step 3: Set other Options
1. **Email Settings**: 1. **Email Settings**:
+1 -1
View File
@@ -51,7 +51,7 @@ class SAMLConfigurationAdmin(admin.ModelAdmin):
search_fields = ['social_app__name', 'idp_id', 'sp_metadata_url'] search_fields = ['social_app__name', 'idp_id', 'sp_metadata_url']
fieldsets = [ fieldsets = [
('Provider Settings', {'fields': ['social_app', 'idp_id', 'idp_cert']}), ('Provider Settings', {'fields': ['social_app', 'idp_id', 'idp_cert', 'sp_cert', 'sp_private_key']}),
('URLs', {'fields': ['sso_url', 'slo_url', 'sp_metadata_url']}), ('URLs', {'fields': ['sso_url', 'slo_url', 'sp_metadata_url']}),
('Group Management', {'fields': ['remove_from_groups', 'save_saml_response_logs']}), ('Group Management', {'fields': ['remove_from_groups', 'save_saml_response_logs']}),
('Attribute Mapping', {'fields': ['uid', 'name', 'email', 'groups', 'first_name', 'last_name', 'user_logo', 'role']}), ('Attribute Mapping', {'fields': ['uid', 'name', 'email', 'groups', 'first_name', 'last_name', 'user_logo', 'role']}),
+5 -9
View File
@@ -53,16 +53,12 @@ def build_sp_config(request, provider_config, org):
"binding": OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT, "binding": OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
}, },
} }
if _sp_config.get("x509cert"):
sp_config["x509cert"] = _sp_config["x509cert"]
if _sp_config.get("private_key"):
sp_config["privateKey"] = _sp_config["private_key"]
avd = provider_config.get("advanced", {}) avd = provider_config.get("advanced", {})
if avd.get("x509cert") is not None:
sp_config["x509cert"] = avd["x509cert"]
if avd.get("x509cert_new"):
sp_config["x509certNew"] = avd["x509cert_new"]
if avd.get("private_key") is not None:
sp_config["privateKey"] = avd["private_key"]
if avd.get("name_id_format") is not None: if avd.get("name_id_format") is not None:
sp_config["NameIDFormat"] = avd["name_id_format"] sp_config["NameIDFormat"] = avd["name_id_format"]
+3 -1
View File
@@ -154,7 +154,9 @@ sls = SLSView.as_view()
class MetadataView(SAMLViewMixin, View): class MetadataView(SAMLViewMixin, View):
def dispatch(self, request, organization_slug): def dispatch(self, request, organization_slug):
provider = self.get_provider(organization_slug) provider = self.get_provider(organization_slug)
config = build_saml_config(self.request, provider.app.settings, organization_slug) custom_configuration = provider.app.saml_configurations.first()
provider_config = custom_configuration.saml_provider_settings if custom_configuration else provider.app.settings
config = build_saml_config(self.request, provider_config, organization_slug)
saml_settings = OneLogin_Saml2_Settings(settings=config, sp_validation_only=True) saml_settings = OneLogin_Saml2_Settings(settings=config, sp_validation_only=True)
metadata = saml_settings.get_sp_metadata() metadata = saml_settings.get_sp_metadata()
errors = saml_settings.validate_metadata(metadata) errors = saml_settings.validate_metadata(metadata)
@@ -0,0 +1,23 @@
# Generated by Django 5.2.6 on 2026-05-31 12:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('saml_auth', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='samlconfiguration',
name='sp_cert',
field=models.TextField(blank=True, help_text='SP x509cert (PEM). Optional; required if SP private key is set.', null=True),
),
migrations.AddField(
model_name='samlconfiguration',
name='sp_private_key',
field=models.TextField(blank=True, help_text='SP private key (PEM). Optional; required if SP certificate is set.', null=True),
),
]
+11
View File
@@ -14,6 +14,8 @@ class SAMLConfiguration(models.Model):
# Certificates # Certificates
idp_cert = models.TextField(help_text='x509cert') idp_cert = models.TextField(help_text='x509cert')
sp_cert = models.TextField(blank=True, null=True, help_text='SP x509cert (PEM). Optional; required if SP private key is set.')
sp_private_key = models.TextField(blank=True, null=True, help_text='SP private key (PEM). Optional; required if SP certificate is set.')
# Attribute Mapping Fields # Attribute Mapping Fields
uid = models.CharField(max_length=100, help_text='eg eduPersonPrincipalName') uid = models.CharField(max_length=100, help_text='eg eduPersonPrincipalName')
@@ -49,6 +51,11 @@ class SAMLConfiguration(models.Model):
if existing_conf.exists(): if existing_conf.exists():
raise ValidationError({'social_app': 'Cannot create configuration for the same social app because one configuration already exists.'}) raise ValidationError({'social_app': 'Cannot create configuration for the same social app because one configuration already exists.'})
if self.sp_cert and not self.sp_private_key:
raise ValidationError({'sp_private_key': 'Required when SP certificate is provided.'})
if self.sp_private_key and not self.sp_cert:
raise ValidationError({'sp_cert': 'Required when SP private key is provided.'})
super().clean() super().clean()
@property @property
@@ -56,6 +63,10 @@ class SAMLConfiguration(models.Model):
# provide settings in a way for Social App SAML provider # provide settings in a way for Social App SAML provider
provider_settings = {} provider_settings = {}
provider_settings["sp"] = {"entity_id": self.sp_metadata_url} provider_settings["sp"] = {"entity_id": self.sp_metadata_url}
if self.sp_cert:
provider_settings["sp"]["x509cert"] = self.sp_cert
if self.sp_private_key:
provider_settings["sp"]["private_key"] = self.sp_private_key
provider_settings["idp"] = {"slo_url": self.slo_url, "sso_url": self.sso_url, "x509cert": self.idp_cert, "entity_id": self.idp_id} provider_settings["idp"] = {"slo_url": self.slo_url, "sso_url": self.sso_url, "x509cert": self.idp_cert, "entity_id": self.idp_id}
provider_settings["attribute_mapping"] = { provider_settings["attribute_mapping"] = {