Skip to content

Commit

Permalink
Compat flag to allow unsigned logout response
Browse files Browse the repository at this point in the history
- Fixes #446
- Closes #590
- Closes #924
  • Loading branch information
AndersAbel committed Apr 14, 2020
2 parents 701d06b + 2abcd52 commit cf1285d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 3 deletions.
1 change: 1 addition & 0 deletions Sustainsys.Saml2.sln
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
Tests\Tests.Shared\Tests.Shared.projitems*{82f84e61-1292-47cf-b0dc-59f26ec56c32}*SharedItemsImports = 13
Tests\Tests.Shared\Tests.Shared.projitems*{c5c43d57-3a9c-4edf-97af-ee55a950284c}*SharedItemsImports = 4
Tests\Tests.Shared\Tests.Shared.projitems*{ff774b2e-51d4-4c64-ba0e-c061683a9b93}*SharedItemsImports = 5
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
8 changes: 8 additions & 0 deletions Sustainsys.Saml2/Configuration/Compatibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,13 @@ public Compatibility(CompatibilityElement configElement)
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Logout")]
public bool EnableLogoutOverPost { get; set; }

/// <summary>
/// SAML2 Specs says in section 4.4.4.2:
/// "... The responder MUST authenticate itself to the requester and ensure message integrity, either by signing the message or using a binding-specific mechanism."
///
/// Unfortunately not all IDP seem to follow the specification. Disables requirement for a signed LogoutResponse.
/// </summary>
public bool AcceptUnsignedLogoutResponses { get; set; }
}
}
6 changes: 5 additions & 1 deletion Sustainsys.Saml2/WebSSO/LogOutCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,17 @@ public CommandResult Run(HttpRequestData request, IOptions options)
var unbindResult = binding.Unbind(request, options);
options.Notifications.MessageUnbound(unbindResult);

VerifyMessageIsSigned(unbindResult, options);
switch (unbindResult.Data.LocalName)
{
case "LogoutRequest":
VerifyMessageIsSigned(unbindResult, options);
commandResult = HandleRequest(unbindResult, request, options);
break;
case "LogoutResponse":
if (!options.SPOptions.Compatibility.AcceptUnsignedLogoutResponses)
{
VerifyMessageIsSigned(unbindResult, options);
}
var storedRequestState = options.Notifications.GetLogoutResponseState(request);
var urls = new Saml2Urls(request, options);
commandResult = HandleResponse(unbindResult, storedRequestState, options, returnUrl, urls);
Expand Down
60 changes: 58 additions & 2 deletions Tests/Tests.Shared/WebSSO/LogoutCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,63 @@ public void LogoutCommand_Run_HandlesLogoutResponse()
actual.Should().BeEquivalentTo(expected);
}

[TestMethod]
public void LogoutCommand_Run_RejectsUnsignedLogoutResponse()
{
var relayState = "MyRelayState";
var response = new Saml2LogoutResponse(Saml2StatusCode.Success)
{
DestinationUrl = new Uri("http://sp.example.com/path/Saml2/logout"),
Issuer = new EntityId("https://idp.example.com"),
InResponseTo = new Saml2Id(),
RelayState = relayState
};

var bindResult = Saml2Binding.Get(Saml2BindingType.HttpRedirect)
.Bind(response);

var request = new HttpRequestData("GET",
bindResult.Location,
"http://sp-internal.example.com/path/Saml2",
null,
new StoredRequestState(null, new Uri("http://loggedout.example.com"), null, null));

var options = StubFactory.CreateOptions();

CommandFactory.GetCommand(CommandFactory.LogoutCommandName)
.Invoking(c => c.Run(request, options))
.Should().Throw<UnsuccessfulSamlOperationException>();
}

[TestMethod]
public void LogoutCommand_Run_AcceptsUnsignedLogoutResponseIfCompatFlagSet()
{
var relayState = "MyRelayState";
var response = new Saml2LogoutResponse(Saml2StatusCode.Success)
{
DestinationUrl = new Uri("http://sp.example.com/path/Saml2/logout"),
Issuer = new EntityId("https://idp.example.com"),
InResponseTo = new Saml2Id(),
RelayState = relayState
};

var bindResult = Saml2Binding.Get(Saml2BindingType.HttpRedirect)
.Bind(response);

var request = new HttpRequestData("GET",
bindResult.Location,
"http://sp-internal.example.com/path/Saml2",
null,
new StoredRequestState(null, new Uri("http://loggedout.example.com"), null, null));

var options = StubFactory.CreateOptions();
options.SPOptions.Compatibility.AcceptUnsignedLogoutResponses = true;

// Should not throw.
CommandFactory.GetCommand(CommandFactory.LogoutCommandName)
.Run(request, options);
}

[TestMethod]
public void LogoutCommand_Run_HandlesLogoutResponse_InPost()
{
Expand Down Expand Up @@ -741,7 +798,6 @@ public void LogoutCommand_Run_IncomingRequest_ThroughRedirectBinding_ThrowsOnMis
.WithMessage("Received a LogoutRequest from https://idp.example.com that cannot be processed because it is not signed.");
}


[TestMethod]
public void LogoutCommand_Run_ThrowsOnLogoutResponseStatusNonSuccess()
{
Expand Down Expand Up @@ -969,7 +1025,7 @@ public void LogoutCommand_Run_ThrowsOnMissingIssuerInReceivedMessage()
MessageName = "SAMLRequest",
SigningCertificate = SignedXmlHelper.TestCert,
DestinationUrl = new Uri("http://localhost"),
XmlData = "<Xml />"
XmlData = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"/>"
};

var url = Saml2Binding.Get(Saml2BindingType.HttpRedirect)
Expand Down

0 comments on commit cf1285d

Please sign in to comment.