Skip to content

Commit 8327721

Browse files
committed
Add support for validating the max length of SAML 2.0 request/response.
Change samples to validate SAML 2.0 request/response. Upgrade TestWebAppCoreNemLogin3Sp to .NET 7.
1 parent af0df69 commit 8327721

File tree

15 files changed

+128
-50
lines changed

15 files changed

+128
-50
lines changed

src/ITfoxtec.Identity.Saml2.Mvc/Extensions/HttpRequestExtensions.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Web;
1+
using ITfoxtec.Identity.Saml2.Schemas;
2+
using System.Web;
23
using System;
34
using System.IO;
45

@@ -12,16 +13,50 @@ public static class HttpRequestExtensions
1213
/// <summary>
1314
/// Converts a System.Web.HttpRequestBase to ITfoxtec.Identity.Saml2.Http.HttpRequest.
1415
/// </summary>
15-
public static Http.HttpRequest ToGenericHttpRequest(this HttpRequestBase request, bool readBodyAsString = false)
16+
public static Http.HttpRequest ToGenericHttpRequest(this HttpRequestBase request, bool readBodyAsString = false, bool validate = false)
1617
{
17-
return new Http.HttpRequest
18+
var samlHttpRequest = new Http.HttpRequest
1819
{
1920
Method = request.HttpMethod,
2021
QueryString = request.Url.Query,
2122
Query = request.QueryString,
2223
Form = "POST".Equals(request.HttpMethod, StringComparison.InvariantCultureIgnoreCase) ? request.Form : null,
2324
Body = ReadBody(request, readBodyAsString)
2425
};
26+
27+
if (validate)
28+
{
29+
var length = 0;
30+
if (!string.IsNullOrEmpty(samlHttpRequest.QueryString))
31+
{
32+
length += samlHttpRequest.QueryString.Length;
33+
}
34+
if (readBodyAsString)
35+
{
36+
if (!string.IsNullOrEmpty(samlHttpRequest.Body))
37+
{
38+
length += samlHttpRequest.Body.Length;
39+
}
40+
}
41+
else
42+
{
43+
if (samlHttpRequest.Form != null)
44+
{
45+
foreach (string item in samlHttpRequest.Form)
46+
{
47+
if (!string.IsNullOrEmpty(item))
48+
{
49+
length += item.Length;
50+
}
51+
}
52+
}
53+
}
54+
if (length > Saml2Constants.RequestResponseMaxLength)
55+
{
56+
throw new Saml2RequestException($"Invalid SAML 2.0 request/response with a length of {length}, max length {Saml2Constants.RequestResponseMaxLength}.");
57+
}
58+
}
59+
return samlHttpRequest;
2560
}
2661

2762
private static string ReadBody(HttpRequestBase request, bool readBodyAsString)

src/ITfoxtec.Identity.Saml2.Mvc/ITfoxtec.Identity.Saml2.Mvc.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.</Descrip
2626
<PackageTags>SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) and Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3 ASP.NET MVC</PackageTags>
2727
<NeutralLanguage>en-US</NeutralLanguage>
2828
<PackageIconUrl>https://itfoxtec.com/favicon.ico</PackageIconUrl>
29-
<AssemblyVersion>4.10.1.0</AssemblyVersion>
30-
<FileVersion>4.10.1.0</FileVersion>
29+
<AssemblyVersion>4.10.2.0</AssemblyVersion>
30+
<FileVersion>4.10.2.0</FileVersion>
3131
<Copyright>Copyright © 2023</Copyright>
32-
<Version>4.10.1</Version>
32+
<Version>4.10.2</Version>
3333
<SignAssembly>true</SignAssembly>
3434
<AssemblyOriginatorKeyFile>ITfoxtec.SAML2.snk</AssemblyOriginatorKeyFile>
3535
<DelaySign>false</DelaySign>

src/ITfoxtec.Identity.Saml2.MvcCore/Extensions/HttpRequestExtensions.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.IO;
88
using System.Threading.Tasks;
9+
using ITfoxtec.Identity.Saml2.Schemas;
910

1011
namespace ITfoxtec.Identity.Saml2.MvcCore
1112
{
@@ -17,29 +18,66 @@ public static class HttpRequestExtensions
1718
/// <summary>
1819
/// Converts a Microsoft.AspNet.Http.HttpRequest to ITfoxtec.Identity.Saml2.Http.HttpRequest.
1920
/// </summary>
20-
public static Http.HttpRequest ToGenericHttpRequest(this HttpRequest request)
21+
public static Http.HttpRequest ToGenericHttpRequest(this HttpRequest request, bool validate = false)
2122
{
22-
return new Http.HttpRequest
23+
var samlHttpRequest = new Http.HttpRequest
2324
{
2425
Method = request.Method,
2526
QueryString = request.QueryString.Value,
2627
Query = ToNameValueCollection(request.Query),
2728
Form = "POST".Equals(request.Method, StringComparison.InvariantCultureIgnoreCase) ? ToNameValueCollection(request.Form) : null
2829
};
30+
31+
if (validate)
32+
{
33+
var length = 0;
34+
if (!string.IsNullOrEmpty(samlHttpRequest.QueryString))
35+
{
36+
length += samlHttpRequest.QueryString.Length;
37+
}
38+
if (samlHttpRequest.Form != null)
39+
{
40+
foreach (string item in samlHttpRequest.Form)
41+
{
42+
if (!string.IsNullOrEmpty(item))
43+
{
44+
length += item.Length;
45+
}
46+
}
47+
}
48+
if (length > Saml2Constants.RequestResponseMaxLength)
49+
{
50+
throw new Saml2RequestException($"Invalid SAML 2.0 request/response with a length of {length}, max length {Saml2Constants.RequestResponseMaxLength}.");
51+
}
52+
}
53+
return samlHttpRequest;
2954
}
3055

3156
/// <summary>
3257
/// Converts a Microsoft.AspNet.Http.HttpRequest to ITfoxtec.Identity.Saml2.Http.HttpRequest.
3358
/// </summary>
34-
public static async Task<Http.HttpRequest> ToGenericHttpRequestAsync(this HttpRequest request, bool readBodyAsString = false)
59+
public static async Task<Http.HttpRequest> ToGenericHttpRequestAsync(this HttpRequest request, bool readBodyAsString = false, bool validate = false)
3560
{
3661
if (readBodyAsString)
3762
{
38-
return new Http.HttpRequest
63+
var samlHttpRequest = new Http.HttpRequest
3964
{
4065
Method = request.Method,
4166
Body = await ReadBodyStringAsync(request)
4267
};
68+
69+
if (validate)
70+
{
71+
if (!string.IsNullOrEmpty(samlHttpRequest.Body))
72+
{
73+
var length = samlHttpRequest.Body.Length;
74+
if (length > Saml2Constants.RequestResponseMaxLength)
75+
{
76+
throw new Saml2RequestException($"Invalid SAML 2.0 request/response with a length of {length}, max length {Saml2Constants.RequestResponseMaxLength}.");
77+
}
78+
}
79+
}
80+
return samlHttpRequest;
4381
}
4482
else
4583
{

src/ITfoxtec.Identity.Saml2.MvcCore/ITfoxtec.Identity.Saml2.MvcCore.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.</Descrip
3030
<PackageTags>SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3 ASP.NET MVC Core</PackageTags>
3131
<NeutralLanguage>en-US</NeutralLanguage>
3232
<PackageIconUrl>https://itfoxtec.com/favicon.ico</PackageIconUrl>
33-
<AssemblyVersion>4.10.1.0</AssemblyVersion>
34-
<FileVersion>4.10.1.0</FileVersion>
33+
<AssemblyVersion>4.10.2.0</AssemblyVersion>
34+
<FileVersion>4.10.2.0</FileVersion>
3535
<Copyright>Copyright © 2023</Copyright>
36-
<Version>4.10.1</Version>
36+
<Version>4.10.2</Version>
3737
<SignAssembly>true</SignAssembly>
3838
<AssemblyOriginatorKeyFile>ITfoxtec.SAML2.snk</AssemblyOriginatorKeyFile>
3939
<DelaySign>false</DelaySign>

src/ITfoxtec.Identity.Saml2/ITfoxtec.Identity.Saml2.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.</Descrip
3131
<PackageTags>SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3</PackageTags>
3232
<NeutralLanguage>en-US</NeutralLanguage>
3333
<PackageIconUrl>https://itfoxtec.com/favicon.ico</PackageIconUrl>
34-
<AssemblyVersion>4.10.1.0</AssemblyVersion>
35-
<FileVersion>4.10.1.0</FileVersion>
34+
<AssemblyVersion>4.10.2.0</AssemblyVersion>
35+
<FileVersion>4.10.2.0</FileVersion>
3636
<Copyright>Copyright © 2023</Copyright>
37-
<Version>4.10.1</Version>
37+
<Version>4.10.2</Version>
3838
<SignAssembly>true</SignAssembly>
3939
<AssemblyOriginatorKeyFile>ITfoxtec.SAML2.snk</AssemblyOriginatorKeyFile>
4040
<DelaySign>false</DelaySign>

src/ITfoxtec.Identity.Saml2/Schemas/Saml2Constants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ namespace ITfoxtec.Identity.Saml2.Schemas
55
{
66
public static class Saml2Constants
77
{
8+
/// <summary>
9+
/// SAML 2.0 request / response max length.
10+
/// </summary>
11+
public const int RequestResponseMaxLength = 100000;
12+
813
/// <summary>
914
/// SAML 2.0 Authentication Type.
1015
/// </summary>

test/TestIdPCore/Controllers/AuthController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public async Task<IActionResult> Login()
4747
var saml2AuthnRequest = new Saml2AuthnRequest(GetRpSaml2Configuration(relyingParty));
4848
try
4949
{
50-
requestBinding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnRequest);
50+
requestBinding.Unbind(Request.ToGenericHttpRequest(validate: true), saml2AuthnRequest);
5151

5252
// **** Handle user login e.g. in GUI ****
5353
// Test user with session index and claims
@@ -108,7 +108,7 @@ public async Task<IActionResult> Logout()
108108
var saml2LogoutRequest = new Saml2LogoutRequest(GetRpSaml2Configuration(relyingParty));
109109
try
110110
{
111-
requestBinding.Unbind(Request.ToGenericHttpRequest(), saml2LogoutRequest);
111+
requestBinding.Unbind(Request.ToGenericHttpRequest(validate: true), saml2LogoutRequest);
112112

113113
// **** Delete user session ****
114114

@@ -125,12 +125,12 @@ public async Task<IActionResult> Logout()
125125

126126
private string ReadRelyingPartyFromLoginRequest<T>(Saml2Binding<T> binding)
127127
{
128-
return binding.ReadSamlRequest(Request.ToGenericHttpRequest(), new Saml2AuthnRequest(GetRpSaml2Configuration()))?.Issuer;
128+
return binding.ReadSamlRequest(Request.ToGenericHttpRequest(validate: true), new Saml2AuthnRequest(GetRpSaml2Configuration()))?.Issuer;
129129
}
130130

131131
private string ReadRelyingPartyFromLogoutRequest<T>(Saml2Binding<T> binding)
132132
{
133-
return binding.ReadSamlRequest(Request.ToGenericHttpRequest(), new Saml2LogoutRequest(GetRpSaml2Configuration()))?.Issuer;
133+
return binding.ReadSamlRequest(Request.ToGenericHttpRequest(validate: true), new Saml2LogoutRequest(GetRpSaml2Configuration()))?.Issuer;
134134
}
135135

136136
private string ReadRelyingPartyFromSoapEnvelopeRequest<T>(ITfoxtec.Identity.Saml2.Http.HttpRequest httpRequest, Saml2Binding<T> binding)

test/TestWebApp/Controllers/AuthController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ public ActionResult AssertionConsumerService()
4545
var binding = new Saml2PostBinding();
4646
var saml2AuthnResponse = new Saml2AuthnResponse(config);
4747

48-
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
48+
binding.ReadSamlResponse(Request.ToGenericHttpRequest(validate: true), saml2AuthnResponse);
4949
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
5050
{
5151
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
5252
}
53-
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
53+
binding.Unbind(Request.ToGenericHttpRequest(validate: true), saml2AuthnResponse);
5454
saml2AuthnResponse.CreateSession(claimsAuthenticationManager: new DefaultClaimsAuthenticationManager());
5555

5656
var relayStateQuery = binding.GetRelayStateQuery();
@@ -74,7 +74,7 @@ public ActionResult Logout()
7474
public ActionResult LoggedOut()
7575
{
7676
var binding = new Saml2PostBinding();
77-
binding.Unbind(Request.ToGenericHttpRequest(), new Saml2LogoutResponse(config));
77+
binding.Unbind(Request.ToGenericHttpRequest(validate: true), new Saml2LogoutResponse(config));
7878

7979
FederatedAuthentication.SessionAuthenticationModule.DeleteSessionTokenCookie();
8080
FederatedAuthentication.SessionAuthenticationModule.SignOut();
@@ -89,7 +89,7 @@ public ActionResult SingleLogout()
8989
var logoutRequest = new Saml2LogoutRequest(config, ClaimsPrincipal.Current);
9090
try
9191
{
92-
requestBinding.Unbind(Request.ToGenericHttpRequest(), logoutRequest);
92+
requestBinding.Unbind(Request.ToGenericHttpRequest(validate: true), logoutRequest);
9393
status = Saml2StatusCodes.Success;
9494
logoutRequest.DeleteSession();
9595
}

test/TestWebAppCore/Controllers/AuthController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ public async Task<IActionResult> AssertionConsumerService()
5050
var binding = new Saml2PostBinding();
5151
var saml2AuthnResponse = new Saml2AuthnResponse(config);
5252

53-
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
53+
binding.ReadSamlResponse(Request.ToGenericHttpRequest(validate: true), saml2AuthnResponse);
5454
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
5555
{
5656
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
5757
}
58-
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
58+
binding.Unbind(Request.ToGenericHttpRequest(validate: true), saml2AuthnResponse);
5959
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal));
6060

6161
var relayStateQuery = binding.GetRelayStateQuery();
@@ -81,7 +81,7 @@ public async Task<IActionResult> Logout()
8181
public IActionResult LoggedOut()
8282
{
8383
var binding = new Saml2PostBinding();
84-
binding.Unbind(Request.ToGenericHttpRequest(), new Saml2LogoutResponse(config));
84+
binding.Unbind(Request.ToGenericHttpRequest(validate: true), new Saml2LogoutResponse(config));
8585

8686
return Redirect(Url.Content("~/"));
8787
}
@@ -94,7 +94,7 @@ public async Task<IActionResult> SingleLogout()
9494
var logoutRequest = new Saml2LogoutRequest(config, User);
9595
try
9696
{
97-
requestBinding.Unbind(Request.ToGenericHttpRequest(), logoutRequest);
97+
requestBinding.Unbind(Request.ToGenericHttpRequest(validate: true), logoutRequest);
9898
status = Saml2StatusCodes.Success;
9999
await logoutRequest.DeleteSession(HttpContext);
100100
}

test/TestWebAppCoreAngularApi/Controllers/AuthController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ public async Task<IActionResult> AssertionConsumerService()
4949
var binding = new Saml2PostBinding();
5050
var saml2AuthnResponse = new Saml2AuthnResponse(config);
5151

52-
binding.ReadSamlResponse(Request.ToGenericHttpRequest(), saml2AuthnResponse);
52+
binding.ReadSamlResponse(Request.ToGenericHttpRequest(validate: true), saml2AuthnResponse);
5353
if (saml2AuthnResponse.Status != Saml2StatusCodes.Success)
5454
{
5555
throw new AuthenticationException($"SAML Response status: {saml2AuthnResponse.Status}");
5656
}
57-
binding.Unbind(Request.ToGenericHttpRequest(), saml2AuthnResponse);
57+
binding.Unbind(Request.ToGenericHttpRequest(validate: true), saml2AuthnResponse);
5858
await saml2AuthnResponse.CreateSession(HttpContext, claimsTransform: (claimsPrincipal) => ClaimsTransform.Transform(claimsPrincipal));
5959

6060
var relayStateQuery = binding.GetRelayStateQuery();
@@ -80,7 +80,7 @@ public async Task<IActionResult> Logout()
8080
public IActionResult LoggedOut()
8181
{
8282
var binding = new Saml2PostBinding();
83-
binding.Unbind(Request.ToGenericHttpRequest(), new Saml2LogoutResponse(config));
83+
binding.Unbind(Request.ToGenericHttpRequest(validate: true), new Saml2LogoutResponse(config));
8484

8585
return Redirect(Url.Content("~/"));
8686
}
@@ -93,7 +93,7 @@ public async Task<IActionResult> SingleLogout()
9393
var logoutRequest = new Saml2LogoutRequest(config, User);
9494
try
9595
{
96-
requestBinding.Unbind(Request.ToGenericHttpRequest(), logoutRequest);
96+
requestBinding.Unbind(Request.ToGenericHttpRequest(validate: true), logoutRequest);
9797
status = Saml2StatusCodes.Success;
9898
await logoutRequest.DeleteSession(HttpContext);
9999
}

0 commit comments

Comments
 (0)