From 9e33cfdf7fcdaeeac958a7951b48545e02abbade Mon Sep 17 00:00:00 2001 From: dorsha Date: Tue, 1 Sep 2026 10:59:55 +0300 Subject: [PATCH] feat(sso): allow disabling SAML AuthnRequest signing per SSO configuration Descope always signs the SAML AuthnRequest it sends to a tenant's IdP. A few IdPs (NetIQ Access Manager among them) reject a signed request outright when their trusted-provider entry holds no signing certificate for Descope, and until now there was no way to opt out. Adds disable_sign_request to SSOSAMLSettings and SSOSAMLSettingsByMetadata. The flag is always sent on configure - the server takes the settings object as a full replacement, so omitting it on an update would silently turn signing back on. Both parameters are appended last to preserve positional compatibility for existing callers, and default to False so every existing configuration keeps signing as it does today. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 10 ++++++++++ descope/management/_sso_settings_base.py | 6 ++++++ descope/management/sso_settings.py | 9 +++++++++ tests/management/test_sso_settings.py | 5 +++++ 4 files changed, 30 insertions(+) diff --git a/README.md b/README.md index 1e949373c..430ece3d5 100644 --- a/README.md +++ b/README.md @@ -957,6 +957,16 @@ descope_client.mgmt.sso.configure_saml_settings_by_metadata( domains=["tenant-users.com"] # Users authentication with these domains will be logged in to this tenant ) +# Descope signs the SAML AuthnRequest it sends to the IdP. A few IdPs reject a signed request because +# their trusted provider entry holds no signing certificate for Descope - pass disable_sign_request +# (available on both settings classes above) to send the request unsigned for that configuration only. +settings = SSOSAMLSettings( + idp_url="https://dummy.com", + idp_entity_id="my-idp-entity-id", + idp_cert="my-idp-certificate", + disable_sign_request=True, +) + # You can Configure SSO OIDC settings for a tenant manually. settings = SSOOIDCSettings( name="myProvider", diff --git a/descope/management/_sso_settings_base.py b/descope/management/_sso_settings_base.py index 43b40a9f3..e6966a9e1 100644 --- a/descope/management/_sso_settings_base.py +++ b/descope/management/_sso_settings_base.py @@ -270,6 +270,9 @@ def _compose_configure_saml_settings_body( "fgaMappings": SSOSettingsBase._fga_mappings_to_dict(settings.fga_mappings), "configFGATenantIDResourcePrefix": settings.config_fga_tenant_id_resource_prefix, "configFGATenantIDResourceSuffix": settings.config_fga_tenant_id_resource_suffix, + # always sent: the server takes the settings object as a full replacement, so omitting the + # flag on an update would silently turn request signing back on + "disableSignRequest": settings.disable_sign_request, }, "redirectUrl": redirect_url, "domains": domains, @@ -300,6 +303,9 @@ def _compose_configure_saml_settings_by_metadata_body( "fgaMappings": SSOSettingsBase._fga_mappings_to_dict(settings.fga_mappings), "configFGATenantIDResourcePrefix": settings.config_fga_tenant_id_resource_prefix, "configFGATenantIDResourceSuffix": settings.config_fga_tenant_id_resource_suffix, + # always sent: the server takes the settings object as a full replacement, so omitting the + # flag on an update would silently turn request signing back on + "disableSignRequest": settings.disable_sign_request, }, "redirectUrl": redirect_url, "domains": domains, diff --git a/descope/management/sso_settings.py b/descope/management/sso_settings.py index 4afbda289..ed2e0c677 100644 --- a/descope/management/sso_settings.py +++ b/descope/management/sso_settings.py @@ -231,6 +231,10 @@ def __init__( # NOTICE - the following fields should be overridden only in case of SSO migration, otherwise, do not modify these fields sp_acs_url: Optional[str] = None, sp_entity_id: Optional[str] = None, + # Leave the SAML AuthnRequest Descope sends to the IdP unsigned. Set it only for IdPs that reject a + # signed request because their trusted provider entry holds no signing certificate for Descope. + # Appended last to preserve positional compatibility for existing callers. + disable_sign_request: bool = False, ): self.idp_url = idp_url self.idp_entity_id = idp_entity_id @@ -245,6 +249,7 @@ def __init__( self.fga_mappings = fga_mappings self.config_fga_tenant_id_resource_prefix = config_fga_tenant_id_resource_prefix self.config_fga_tenant_id_resource_suffix = config_fga_tenant_id_resource_suffix + self.disable_sign_request = disable_sign_request class SSOSAMLSettingsByMetadata: @@ -268,6 +273,9 @@ def __init__( # IdP entity ID - set so IdP-initiated login can resolve the tenant by the SAML response issuer. # Appended last to preserve positional compatibility for existing callers. idp_entity_id: Optional[str] = None, + # Leave the SAML AuthnRequest Descope sends to the IdP unsigned. Set it only for IdPs that reject a + # signed request because their trusted provider entry holds no signing certificate for Descope. + disable_sign_request: bool = False, ): self.idp_metadata_url = idp_metadata_url self.idp_entity_id = idp_entity_id @@ -280,6 +288,7 @@ def __init__( self.fga_mappings = fga_mappings self.config_fga_tenant_id_resource_prefix = config_fga_tenant_id_resource_prefix self.config_fga_tenant_id_resource_suffix = config_fga_tenant_id_resource_suffix + self.disable_sign_request = disable_sign_request class SSOSettings(SSOSettingsBase, HTTPBase): diff --git a/tests/management/test_sso_settings.py b/tests/management/test_sso_settings.py index 6882b4871..3af727cee 100644 --- a/tests/management/test_sso_settings.py +++ b/tests/management/test_sso_settings.py @@ -286,6 +286,7 @@ async def test_configure_saml_settings(self, client_factory): "fgaMappings": None, "configFGATenantIDResourcePrefix": None, "configFGATenantIDResourceSuffix": None, + "disableSignRequest": False, }, "redirectUrl": "https://redirect.com", "domains": ["domain.com"], @@ -642,6 +643,7 @@ async def test_configure_saml_settings_by_metadata(self, client_factory): "fgaMappings": None, "configFGATenantIDResourcePrefix": None, "configFGATenantIDResourceSuffix": None, + "disableSignRequest": False, }, "redirectUrl": "https://redirect.com", "domains": ["domain.com"], @@ -711,6 +713,7 @@ async def test_configure_saml_settings_with_additional_certs(self, client_factor "fgaMappings": None, "configFGATenantIDResourcePrefix": None, "configFGATenantIDResourceSuffix": None, + "disableSignRequest": False, }, "redirectUrl": "https://redirect.com", "domains": ["domain.com"], @@ -831,6 +834,7 @@ async def test_configure_saml_settings_with_fga_mappings(self, client_factory): }, "configFGATenantIDResourcePrefix": "tenant:", "configFGATenantIDResourceSuffix": "", + "disableSignRequest": False, }, "redirectUrl": "https://redirect.com", "domains": ["domain.com"], @@ -898,6 +902,7 @@ async def test_configure_saml_settings_by_metadata_with_fga_mappings(self, clien }, "configFGATenantIDResourcePrefix": "tenant:", "configFGATenantIDResourceSuffix": "-suffix", + "disableSignRequest": False, }, "redirectUrl": None, "domains": None,