Compare commits

...

2 Commits

Author SHA1 Message Date
semantic-release-bot 51b1097509 chore(release): 8.2.0 [skip ci]
## [8.2.0](https://github.com/mediacms-io/mediacms/compare/v8.1.3...v8.2.0) (2026-05-31)

### Features

* configure SP certificate and private key via SAMLConfiguration ([#1531](https://github.com/mediacms-io/mediacms/issues/1531)) ([95644dc](https://github.com/mediacms-io/mediacms/commit/95644dc9615f428191d9fda0847c1b91a0b094a5))
2026-05-31 13:17:18 +00:00
Markos Gogoulos 95644dc961 feat: configure SP certificate and private key via SAMLConfiguration (#1531) 2026-05-31 16:16:46 +03:00
9 changed files with 53 additions and 13 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## [8.2.0](https://github.com/mediacms-io/mediacms/compare/v8.1.3...v8.2.0) (2026-05-31)
### Features
* configure SP certificate and private key via SAMLConfiguration ([#1531](https://github.com/mediacms-io/mediacms/issues/1531)) ([95644dc](https://github.com/mediacms-io/mediacms/commit/95644dc9615f428191d9fda0847c1b91a0b094a5))
## [8.1.3](https://github.com/mediacms-io/mediacms/compare/v8.1.2...v8.1.3) (2026-05-19) ## [8.1.3](https://github.com/mediacms-io/mediacms/compare/v8.1.2...v8.1.3) (2026-05-19)
### Bug Fixes ### Bug Fixes
+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
@@ -1,6 +1,6 @@
{ {
"name": "mediacms", "name": "mediacms",
"version": "8.1.3", "version": "8.2.0",
"devDependencies": { "devDependencies": {
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
+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"] = {