From 5d0b1c5855edba1ac64c7d7c6bde6f2bf3d3d13a Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 18 Sep 2026 16:33:21 +0300 Subject: [PATCH] Read SAML parameters from their binding in the .NET Fedlet; run e2e scripts without a shell The Fedlet service-provider utility read SAMLRequest, SAMLResponse and SAMLart through HttpRequest's indexer, which also searches cookies and server variables; a SAML binding carries them only in the form of a POST or the query string of a GET, and GetBindingParameter reads them from there. The sample application no longer enables directory browsing, and the SAML e2e spec passes the setup script path to bash as an argument instead of interpolating it into a shell command line. --- e2e/saml/saml-test.spec.mjs | 5 ++- .../source/Saml2/ServiceProviderUtility.cs | 38 +++++++++++++------ .../csharpsource/Fedlet/SampleApp/Web.config | 2 +- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/e2e/saml/saml-test.spec.mjs b/e2e/saml/saml-test.spec.mjs index 4206397927..ddac492c8e 100644 --- a/e2e/saml/saml-test.spec.mjs +++ b/e2e/saml/saml-test.spec.mjs @@ -17,7 +17,7 @@ // openam.spec.mjs – ESM edition import { test, expect } from "@playwright/test"; -import { execSync } from "child_process"; +import { execFileSync } from "child_process"; import { resolve } from "path"; import { fileURLToPath } from "url"; import { PASSWORD, USERNAME } from "../common/openam-commons.mjs"; @@ -51,7 +51,8 @@ const SEL = { const execScript = (scriptPath) => { try { - execSync(`bash "${scriptPath}"`, { + // No shell: the script path is handed to bash as an argument, not interpolated. + execFileSync("bash", [scriptPath], { encoding: "utf-8", timeout: 300_000, // 5 minutes max stdio: "inherit", diff --git a/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/Fedlet/source/Saml2/ServiceProviderUtility.cs b/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/Fedlet/source/Saml2/ServiceProviderUtility.cs index 5c9994aaeb..33751dade2 100644 --- a/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/Fedlet/source/Saml2/ServiceProviderUtility.cs +++ b/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/Fedlet/source/Saml2/ServiceProviderUtility.cs @@ -26,6 +26,7 @@ */ /* * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ using System; @@ -275,16 +276,16 @@ public AuthnResponse GetAuthnResponse(HttpContext context) HttpRequest request = context.Request; // Check if a saml response was received... - if (string.IsNullOrEmpty(request[Saml2Constants.ResponseParameter]) - && string.IsNullOrEmpty(request[Saml2Constants.ArtifactParameter])) + if (string.IsNullOrEmpty(GetBindingParameter(request, Saml2Constants.ResponseParameter)) + && string.IsNullOrEmpty(GetBindingParameter(request, Saml2Constants.ArtifactParameter))) { throw new ServiceProviderUtilityException(Resources.ServiceProviderUtilityNoSamlResponseReceived); } // Obtain AuthnResponse object from either HTTP-POST or HTTP-Artifact - if (request[Saml2Constants.ResponseParameter] != null) + if (GetBindingParameter(request, Saml2Constants.ResponseParameter) != null) { - string samlResponse = Saml2Utils.ConvertFromBase64(request[Saml2Constants.ResponseParameter]); + string samlResponse = Saml2Utils.ConvertFromBase64(GetBindingParameter(request, Saml2Constants.ResponseParameter)); authnResponse = new AuthnResponse(samlResponse); XmlDocument xmlDoc = (XmlDocument)authnResponse.XmlDom; @@ -292,9 +293,9 @@ public AuthnResponse GetAuthnResponse(HttpContext context) logMessage.Append("AuthnResponse:\r\n").Append(xmlDoc.OuterXml); FedletLogger.Info(logMessage.ToString()); } - else if (request[Saml2Constants.ArtifactParameter] != null) + else if (GetBindingParameter(request, Saml2Constants.ArtifactParameter) != null) { - Artifact artifact = new Artifact(request[Saml2Constants.ArtifactParameter]); + Artifact artifact = new Artifact(GetBindingParameter(request, Saml2Constants.ArtifactParameter)); artifactResponse = this.GetArtifactResponse(artifact); authnResponse = artifactResponse.AuthnResponse; @@ -357,14 +358,14 @@ public LogoutRequest GetLogoutRequest(HttpContext context) // Obtain the LogoutRequest object... if (request.HttpMethod == "GET") { - samlRequest = Saml2Utils.ConvertFromBase64Decompress(request[Saml2Constants.RequestParameter]); + samlRequest = Saml2Utils.ConvertFromBase64Decompress(GetBindingParameter(request, Saml2Constants.RequestParameter)); } else if (request.HttpMethod == "POST") { // something posted...check if soap vs form post - if (!String.IsNullOrEmpty(request[Saml2Constants.RequestParameter])) + if (!String.IsNullOrEmpty(GetBindingParameter(request, Saml2Constants.RequestParameter))) { - samlRequest = Saml2Utils.ConvertFromBase64(request[Saml2Constants.RequestParameter]); + samlRequest = Saml2Utils.ConvertFromBase64(GetBindingParameter(request, Saml2Constants.RequestParameter)); } else { @@ -441,7 +442,7 @@ public LogoutResponse GetLogoutResponse(HttpContext context) HttpRequest request = context.Request; // Check if a saml response was received... - if (String.IsNullOrEmpty(request[Saml2Constants.ResponseParameter])) + if (String.IsNullOrEmpty(GetBindingParameter(request, Saml2Constants.ResponseParameter))) { throw new ServiceProviderUtilityException(Resources.ServiceProviderUtilityNoSamlResponseReceived); } @@ -449,12 +450,12 @@ public LogoutResponse GetLogoutResponse(HttpContext context) // Obtain the LogoutRequest object... if (request.HttpMethod == "GET") { - string samlResponse = Saml2Utils.ConvertFromBase64Decompress(request[Saml2Constants.ResponseParameter]); + string samlResponse = Saml2Utils.ConvertFromBase64Decompress(GetBindingParameter(request, Saml2Constants.ResponseParameter)); logoutResponse = new LogoutResponse(samlResponse); } else { - string samlResponse = Saml2Utils.ConvertFromBase64(request[Saml2Constants.ResponseParameter]); + string samlResponse = Saml2Utils.ConvertFromBase64(GetBindingParameter(request, Saml2Constants.ResponseParameter)); logoutResponse = new LogoutResponse(samlResponse); } @@ -1679,6 +1680,19 @@ public void ValidateForRedirect(LogoutResponse logoutResponse, ICollection logou #region Static Private Methods + /// + /// Gets a SAML message parameter from where its binding carries it: the form of a + /// POST, the query string of a GET. The request's own indexer would also consult + /// cookies and server variables, which no SAML binding uses. + /// + /// The current request. + /// The parameter name, for example SAMLResponse. + /// The parameter value, or null if the request does not carry it. + private static string GetBindingParameter(HttpRequest request, string name) + { + return request.HttpMethod == "POST" ? request.Form[name] : request.QueryString[name]; + } + /// /// Checks the time condition of the given AuthnResponse. /// diff --git a/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/SampleApp/Web.config b/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/SampleApp/Web.config index f30f87103a..d3d74780f8 100644 --- a/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/SampleApp/Web.config +++ b/openam-federation/openam-federation-library/src/main/csharpsource/Fedlet/SampleApp/Web.config @@ -12,6 +12,6 @@ - + \ No newline at end of file