mirror of
https://github.com/DuendeSoftware/products
synced 2026-05-24 09:28:24 +00:00
Eliminated odd value types to remain more consistent with code base
This commit is contained in:
parent
c0069b8c89
commit
64bda6c985
40 changed files with 222 additions and 303 deletions
|
|
@ -118,7 +118,7 @@ internal class EndSessionCallbackHttpWriter : IHttpResponseWriter<EndSessionCall
|
|||
switch (samlFrontChannelLogout.SamlBinding)
|
||||
{
|
||||
case SamlBinding.HttpPost:
|
||||
var autoPostFormContent = HttpResponseBindings.GenerateAutoPostForm(SamlMessageName.SamlRequest, samlFrontChannelLogout.EncodedContent, samlFrontChannelLogout.Destination, samlFrontChannelLogout.RelayState, includeCsp: true);
|
||||
var autoPostFormContent = HttpResponseBindings.GenerateAutoPostForm(SamlConstants.RequestProperties.SAMLRequest, samlFrontChannelLogout.EncodedContent, samlFrontChannelLogout.Destination, samlFrontChannelLogout.RelayState, includeCsp: true);
|
||||
sb.Append(CultureInfo.InvariantCulture, $"<iframe sandbox='allow-forms allow-scripts allow-same-origin' srcdoc='{HtmlEncoder.Default.Encode(autoPostFormContent)}'></iframe>");
|
||||
break;
|
||||
case SamlBinding.HttpRedirect:
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Duende.IdentityServer.Internal.Saml.Infrastructure;
|
|||
|
||||
internal static class HttpResponseBindings
|
||||
{
|
||||
internal static string GenerateAutoPostForm(SamlMessageName messageName, string encodedMessage, Uri destination, string? relayState, bool includeCsp = false)
|
||||
internal static string GenerateAutoPostForm(string messageName, string encodedMessage, Uri destination, string? relayState, bool includeCsp = false)
|
||||
{
|
||||
var relayStateField = relayState == null
|
||||
? string.Empty
|
||||
|
|
@ -30,7 +30,7 @@ internal static class HttpResponseBindings
|
|||
<p><strong>Note:</strong> Since your browser does not support JavaScript, you must press the button below to proceed.</p>
|
||||
</noscript>
|
||||
<form method=""post"" action=""{HtmlEncoder.Default.Encode(destination.ToString())}"">
|
||||
<input type=""hidden"" name=""{messageName.Value}"" value=""{HtmlEncoder.Default.Encode(encodedMessage)}"" />
|
||||
<input type=""hidden"" name=""{messageName}"" value=""{HtmlEncoder.Default.Encode(encodedMessage)}"" />
|
||||
{relayStateField}
|
||||
<noscript>
|
||||
<input type=""submit"" value=""Continue"" />
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ internal interface ISamlRequest
|
|||
{
|
||||
internal static abstract string MessageName { get; }
|
||||
internal string Issuer { get; }
|
||||
internal SamlVersion Version { get; }
|
||||
internal string Version { get; }
|
||||
internal DateTime IssueInstant { get; }
|
||||
internal Uri? Destination { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using System.Xml.Linq;
|
|||
using Duende.IdentityServer.Endpoints.Results;
|
||||
using Duende.IdentityServer.Hosting;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Saml.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.Infrastructure;
|
||||
|
|
@ -27,7 +26,7 @@ internal class SamlErrorResponse : EndpointResult<SamlErrorResponse>
|
|||
/// <summary>
|
||||
/// Gets the SAML status code for the error.
|
||||
/// </summary>
|
||||
public required SamlStatusCode StatusCode { get; init; }
|
||||
public required string StatusCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the human-readable error message.
|
||||
|
|
@ -57,7 +56,7 @@ internal class SamlErrorResponse : EndpointResult<SamlErrorResponse>
|
|||
/// <summary>
|
||||
/// Gets an optional secondary status code for more specific error information.
|
||||
/// </summary>
|
||||
public SamlStatusCode? SubStatusCode { get; init; }
|
||||
public string? SubStatusCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Service Provider where the response will be sent.
|
||||
|
|
@ -88,7 +87,7 @@ internal class SamlErrorResponse : EndpointResult<SamlErrorResponse>
|
|||
var encodedResponse = Convert.ToBase64String(Encoding.UTF8.GetBytes(stringWriter.ToString()));
|
||||
|
||||
// Generate HTML form that auto-submits to the ACS URL
|
||||
var html = HttpResponseBindings.GenerateAutoPostForm(SamlMessageName.SamlResponse, encodedResponse, result.AssertionConsumerServiceUrl,
|
||||
var html = HttpResponseBindings.GenerateAutoPostForm(SamlConstants.RequestProperties.SAMLResponse, encodedResponse, result.AssertionConsumerServiceUrl,
|
||||
result.RelayState);
|
||||
|
||||
httpContext.Response.ContentType = "text/html";
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ internal class SamlErrorResponseXmlSerializer : ISamlResultSerializer<SamlErrorR
|
|||
{
|
||||
public XElement Serialize(SamlErrorResponse result)
|
||||
{
|
||||
var responseId = ResponseId.New().ToString();
|
||||
var responseId = SamlIds.NewResponseId();
|
||||
var issueInstant = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture);
|
||||
|
||||
var protocolNs = XNamespace.Get(SamlConstants.Namespaces.Protocol);
|
||||
|
|
@ -19,14 +19,14 @@ internal class SamlErrorResponseXmlSerializer : ISamlResultSerializer<SamlErrorR
|
|||
|
||||
// Build Status element
|
||||
var statusCodeElement = new XElement(protocolNs + "StatusCode",
|
||||
new XAttribute("Value", result.StatusCode.ToString()));
|
||||
new XAttribute("Value", result.StatusCode));
|
||||
|
||||
// Add sub-status code if provided
|
||||
if (result.SubStatusCode?.Value != null)
|
||||
if (result.SubStatusCode != null)
|
||||
{
|
||||
statusCodeElement.Add(
|
||||
new XElement(protocolNs + "StatusCode",
|
||||
new XAttribute("Value", result.SubStatusCode.Value.ToString())));
|
||||
new XAttribute("Value", result.SubStatusCode)));
|
||||
}
|
||||
|
||||
var statusElement = new XElement(protocolNs + "Status",
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ internal abstract class SamlRequestProcessorBase<TMessage, TRequest, TSuccess>(
|
|||
Type = SamlRequestErrorType.Protocol,
|
||||
ProtocolError = new SamlProtocolError<TRequest>(sp, request, new SamlError
|
||||
{
|
||||
StatusCode = SamlStatusCode.Requester,
|
||||
StatusCode = SamlStatusCodes.Requester,
|
||||
Message = $"Unsupported binding for signature validation: {request.Binding}"
|
||||
})
|
||||
};
|
||||
|
|
|
|||
|
|
@ -37,17 +37,17 @@ internal class SamlRequestSignatureValidator<TRequest, TSamlRequest>(TimeProvide
|
|||
|
||||
if (string.IsNullOrEmpty(signature))
|
||||
{
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Requester, Message = "Missing signature parameter" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Requester, Message = "Missing signature parameter" });
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(sigAlg))
|
||||
{
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Requester, Message = "Missing signature algorithm parameter" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Requester, Message = "Missing signature algorithm parameter" });
|
||||
}
|
||||
|
||||
if (!SupportedAlgorithms.Contains(sigAlg))
|
||||
{
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Requester, Message = $"Unsupported signature algorithm: {sigAlg}" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Requester, Message = $"Unsupported signature algorithm: {sigAlg}" });
|
||||
}
|
||||
|
||||
// re-create the querystring part that is signed. The spec dictates the exact way this is to be done:
|
||||
|
|
@ -105,7 +105,7 @@ internal class SamlRequestSignatureValidator<TRequest, TSamlRequest>(TimeProvide
|
|||
}
|
||||
catch (XmlException ex)
|
||||
{
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Requester, Message = $"Invalid XML: {ex.Message}" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Requester, Message = $"Invalid XML: {ex.Message}" });
|
||||
}
|
||||
|
||||
// Find signature element
|
||||
|
|
@ -115,14 +115,14 @@ internal class SamlRequestSignatureValidator<TRequest, TSamlRequest>(TimeProvide
|
|||
var signatureNode = doc.SelectSingleNode("//ds:Signature", nsmgr);
|
||||
if (signatureNode == null)
|
||||
{
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Requester, Message = "Signature element not found" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Requester, Message = "Signature element not found" });
|
||||
}
|
||||
|
||||
// Get the request ID that must be signed
|
||||
var requestId = doc.DocumentElement?.GetAttribute("ID");
|
||||
if (string.IsNullOrEmpty(requestId))
|
||||
{
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Requester, Message = $"{TSamlRequest.MessageName} missing ID attribute" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Requester, Message = $"{TSamlRequest.MessageName} missing ID attribute" });
|
||||
}
|
||||
|
||||
return ValidateWithCertificates(
|
||||
|
|
@ -158,7 +158,7 @@ internal class SamlRequestSignatureValidator<TRequest, TSamlRequest>(TimeProvide
|
|||
var validCertificates = serviceProvider.SigningCertificates?.Where(cert => ValidateCertificate(cert).Success).ToList();
|
||||
if (validCertificates == null || validCertificates.Count == 0)
|
||||
{
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Responder, Message = "No valid certificates configured for service provider" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Responder, Message = "No valid certificates configured for service provider" });
|
||||
}
|
||||
|
||||
foreach (var cert in validCertificates)
|
||||
|
|
@ -169,7 +169,7 @@ internal class SamlRequestSignatureValidator<TRequest, TSamlRequest>(TimeProvide
|
|||
}
|
||||
}
|
||||
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCode.Requester, Message = "Invalid signature" });
|
||||
return Result<bool, SamlError>.FromError(new SamlError { StatusCode = SamlStatusCodes.Requester, Message = "Invalid signature" });
|
||||
}
|
||||
|
||||
private Result<bool, string> ValidateCertificate(X509Certificate2 certificate)
|
||||
|
|
|
|||
|
|
@ -20,19 +20,19 @@ internal class SamlRequestValidator(TimeProvider timeProvider, IOptions<SamlOpti
|
|||
/// Validates version, issue instant, and destination for a SAML request
|
||||
/// </summary>
|
||||
internal SamlValidationError? ValidateCommonFields(
|
||||
SamlVersion version,
|
||||
string version,
|
||||
DateTime issueInstant,
|
||||
Uri? destination,
|
||||
SamlServiceProvider serviceProvider,
|
||||
string expectedDestination)
|
||||
{
|
||||
// Version validation
|
||||
if (version != SamlVersion.V2)
|
||||
if (version != SamlVersions.V2)
|
||||
{
|
||||
return new SamlValidationError
|
||||
{
|
||||
Message = "Only Version 2.0 is supported",
|
||||
StatusCode = SamlStatusCode.VersionMismatch
|
||||
StatusCode = SamlStatusCodes.VersionMismatch
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ internal class SamlRequestValidator(TimeProvider timeProvider, IOptions<SamlOpti
|
|||
{
|
||||
return new SamlValidationError
|
||||
{
|
||||
StatusCode = SamlStatusCode.Requester,
|
||||
StatusCode = SamlStatusCodes.Requester,
|
||||
Message = "Request IssueInstant is in the future"
|
||||
};
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ internal class SamlRequestValidator(TimeProvider timeProvider, IOptions<SamlOpti
|
|||
{
|
||||
return new SamlValidationError
|
||||
{
|
||||
StatusCode = SamlStatusCode.Requester,
|
||||
StatusCode = SamlStatusCodes.Requester,
|
||||
Message = "Request has expired (IssueInstant too old)"
|
||||
};
|
||||
}
|
||||
|
|
@ -67,7 +67,7 @@ internal class SamlRequestValidator(TimeProvider timeProvider, IOptions<SamlOpti
|
|||
{
|
||||
return new SamlValidationError
|
||||
{
|
||||
StatusCode = SamlStatusCode.Requester,
|
||||
StatusCode = SamlStatusCodes.Requester,
|
||||
Message = $"Invalid destination. Expected '{expectedDestination}'"
|
||||
};
|
||||
}
|
||||
|
|
@ -83,6 +83,6 @@ internal class SamlRequestValidator(TimeProvider timeProvider, IOptions<SamlOpti
|
|||
internal class SamlValidationError
|
||||
{
|
||||
internal required string Message { get; init; }
|
||||
internal SamlStatusCode StatusCode { get; init; } = SamlStatusCode.Requester;
|
||||
internal SamlStatusCode? SubStatusCode { get; init; }
|
||||
internal string StatusCode { get; init; } = SamlStatusCodes.Requester;
|
||||
internal string? SubStatusCode { get; init; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml;
|
||||
|
||||
internal readonly record struct SamlMessageName(string Value)
|
||||
{
|
||||
public static readonly SamlMessageName SamlResponse = new("SAMLResponse");
|
||||
|
||||
public static readonly SamlMessageName SamlRequest = new("SAMLRequest");
|
||||
|
||||
public static implicit operator SamlMessageName(string value) => new(value);
|
||||
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ internal class SamlResponseBuilder(
|
|||
ServiceProvider = serviceProvider,
|
||||
Binding = serviceProvider.AssertionConsumerServiceBinding,
|
||||
StatusCode = error.StatusCode,
|
||||
SubStatusCode = error.SubStatusCode != null ? new SamlStatusCode(error.SubStatusCode) : null,
|
||||
SubStatusCode = error.SubStatusCode,
|
||||
Message = error.Message,
|
||||
AssertionConsumerServiceUrl = acsUrl,
|
||||
Issuer = serverUrls.Origin, // Todo: not sure if this is a valid issuer
|
||||
|
|
@ -153,8 +153,8 @@ internal class SamlResponseBuilder(
|
|||
Issuer = issuer,
|
||||
Status = new Status
|
||||
{
|
||||
StatusCode = SamlStatusCode.Success,
|
||||
NestedStatusCode = samlAuthenticationState.Request?.RequestedAuthnContext != null && !samlAuthenticationState.RequestedAuthnContextRequirementsWereMet ? (string)SamlStatusCode.NoAuthnContext : null,
|
||||
StatusCode = SamlStatusCodes.Success,
|
||||
NestedStatusCode = samlAuthenticationState.Request?.RequestedAuthnContext != null && !samlAuthenticationState.RequestedAuthnContextRequirementsWereMet ? SamlStatusCodes.NoAuthnContext : null,
|
||||
},
|
||||
Assertion = new Assertion
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
using Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
using Duende.IdentityServer.Saml.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleLogout;
|
||||
|
|
@ -32,7 +30,7 @@ internal static partial class Log
|
|||
EventName = nameof(ParsedLogoutRequest),
|
||||
Message = $"Parsed LogoutRequest. ID: {{{SingleLogoutLogParameters.RequestId}}}, Issuer: {{{SingleLogoutLogParameters.Issuer}}}, SessionIndex: {{{SingleLogoutLogParameters.SessionIndex}}}"
|
||||
)]
|
||||
internal static partial void ParsedLogoutRequest(this ILogger logger, LogLevel logLevel, RequestId requestId, string issuer, string sessionIndex);
|
||||
internal static partial void ParsedLogoutRequest(this ILogger logger, LogLevel logLevel, string requestId, string issuer, string sessionIndex);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(FailedToParseLogoutRequest),
|
||||
|
|
@ -49,12 +47,12 @@ internal static partial class Log
|
|||
[LoggerMessage(
|
||||
EventName = nameof(ReceivedLogoutRequest),
|
||||
Message = $"Received SAML LogoutRequest from {{{SingleLogoutLogParameters.Issuer}}}. RequestId: {{{SingleLogoutLogParameters.RequestId}}}, SessionIndex: {{{SingleLogoutLogParameters.SessionIndex}}}")]
|
||||
internal static partial void ReceivedLogoutRequest(this ILogger logger, LogLevel logLevel, string issuer, RequestId requestId, string sessionIndex);
|
||||
internal static partial void ReceivedLogoutRequest(this ILogger logger, LogLevel logLevel, string issuer, string requestId, string sessionIndex);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SuccessfullyProcessedLogoutRequest),
|
||||
Message = $"Logout request {{{SingleLogoutLogParameters.RequestId}}} with session index {{{SingleLogoutLogParameters.SessionIndex}}} processed successfully")]
|
||||
internal static partial void SuccessfullyProcessedLogoutRequest(this ILogger logger, LogLevel logLevel, RequestId requestId, string sessionIndex);
|
||||
internal static partial void SuccessfullyProcessedLogoutRequest(this ILogger logger, LogLevel logLevel, string requestId, string sessionIndex);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutValidationError),
|
||||
|
|
@ -74,17 +72,17 @@ internal static partial class Log
|
|||
[LoggerMessage(
|
||||
EventName = nameof(ProcessingSamlLogoutRequest),
|
||||
Message = $"Processing LogoutRequest {{{SingleLogoutLogParameters.RequestId}}} from SP: {{{SingleLogoutLogParameters.SpName}}} ({{{SingleLogoutLogParameters.Issuer}}})")]
|
||||
internal static partial void ProcessingSamlLogoutRequest(this ILogger logger, LogLevel logLevel, RequestId requestId, string spName, string issuer);
|
||||
internal static partial void ProcessingSamlLogoutRequest(this ILogger logger, LogLevel logLevel, string requestId, string spName, string issuer);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutRequestReceivedButNoActiveUserSession),
|
||||
Message = $"LogoutRequest {{{SingleLogoutLogParameters.RequestId}}} received from {{{SingleLogoutLogParameters.Issuer}}} but no active user session found")]
|
||||
internal static partial void SamlLogoutRequestReceivedButNoActiveUserSession(this ILogger logger, LogLevel logLevel, RequestId requestId, string issuer);
|
||||
internal static partial void SamlLogoutRequestReceivedButNoActiveUserSession(this ILogger logger, LogLevel logLevel, string requestId, string issuer);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutRequestReceivedWithWrongSessionIndex),
|
||||
Message = $"SessionIndex mismatch. Request: {{{SingleLogoutLogParameters.RequestId}}}, SessionIndex: {{{SingleLogoutLogParameters.SessionIndex}}}")]
|
||||
internal static partial void SamlLogoutRequestReceivedWithWrongSessionIndex(this ILogger logger, LogLevel logLevel, RequestId requestId, string sessionIndex);
|
||||
internal static partial void SamlLogoutRequestReceivedWithWrongSessionIndex(this ILogger logger, LogLevel logLevel, string requestId, string sessionIndex);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutRedirectToLogoutPage),
|
||||
|
|
@ -99,7 +97,7 @@ internal static partial class Log
|
|||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutRequestExpired),
|
||||
Message = $"LogoutRequest {{{SingleLogoutLogParameters.RequestId}}} expired. NotOnOrAfter: {{{SingleLogoutLogParameters.NotOnOrAfter}}}")]
|
||||
internal static partial void SamlLogoutRequestExpired(this ILogger logger, LogLevel logLevel, RequestId requestId, DateTime notOnOrAfter);
|
||||
internal static partial void SamlLogoutRequestExpired(this ILogger logger, LogLevel logLevel, string requestId, DateTime notOnOrAfter);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutSignatureValidationFailed),
|
||||
|
|
@ -129,22 +127,22 @@ internal static partial class Log
|
|||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutUnsupportedVersion),
|
||||
Message = $"LogoutRequest has unsupported SAML version: {{{SingleLogoutLogParameters.Version}}}. Only 2.0 is supported")]
|
||||
internal static partial void SamlLogoutUnsupportedVersion(this ILogger logger, LogLevel logLevel, SamlVersion version);
|
||||
internal static partial void SamlLogoutUnsupportedVersion(this ILogger logger, LogLevel logLevel, string version);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutRequestIssueInstantInFuture),
|
||||
Message = $"LogoutRequest {{{SingleLogoutLogParameters.RequestId}}} has IssueInstant in the future: {{{SingleLogoutLogParameters.IssueInstant}}}")]
|
||||
internal static partial void SamlLogoutRequestIssueInstantInFuture(this ILogger logger, LogLevel logLevel, RequestId requestId, DateTime issueInstant);
|
||||
internal static partial void SamlLogoutRequestIssueInstantInFuture(this ILogger logger, LogLevel logLevel, string requestId, DateTime issueInstant);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutRequestIssueInstantTooOld),
|
||||
Message = $"LogoutRequest {{{SingleLogoutLogParameters.RequestId}}} has IssueInstant too old (expired): {{{SingleLogoutLogParameters.IssueInstant}}}")]
|
||||
internal static partial void SamlLogoutRequestIssueInstantTooOld(this ILogger logger, LogLevel logLevel, RequestId requestId, DateTime issueInstant);
|
||||
internal static partial void SamlLogoutRequestIssueInstantTooOld(this ILogger logger, LogLevel logLevel, string requestId, DateTime issueInstant);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(SamlLogoutRequestInvalidDestination),
|
||||
Message = $"LogoutRequest {{{SingleLogoutLogParameters.RequestId}}} has invalid Destination. Received: {{{SingleLogoutLogParameters.Destination}}}, Expected: {{{SingleLogoutLogParameters.ExpectedDestination}}}")]
|
||||
internal static partial void SamlLogoutRequestInvalidDestination(this ILogger logger, LogLevel logLevel, RequestId requestId, Uri destination, string expectedDestination);
|
||||
internal static partial void SamlLogoutRequestInvalidDestination(this ILogger logger, LogLevel logLevel, string requestId, Uri destination, string expectedDestination);
|
||||
|
||||
[LoggerMessage(
|
||||
EventName = nameof(ProcessingSamlLogoutCallback),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ internal class LogoutResponseBuilder(
|
|||
TimeProvider timeProvider)
|
||||
{
|
||||
internal async Task<LogoutResponse> BuildSuccessResponseAsync(
|
||||
RequestId logoutRequestId,
|
||||
string logoutRequestId,
|
||||
SamlServiceProvider serviceProvider,
|
||||
string? relayState)
|
||||
{
|
||||
|
|
@ -24,15 +24,14 @@ internal class LogoutResponseBuilder(
|
|||
|
||||
return new LogoutResponse
|
||||
{
|
||||
Id = ResponseId.New(),
|
||||
Version = SamlVersion.V2,
|
||||
Id = SamlIds.NewResponseId(),
|
||||
IssueInstant = timeProvider.GetUtcNow().UtcDateTime,
|
||||
Destination = destination.Location,
|
||||
Issuer = issuer,
|
||||
InResponseTo = logoutRequestId.ToString(),
|
||||
InResponseTo = logoutRequestId,
|
||||
Status = new Status
|
||||
{
|
||||
StatusCode = SamlStatusCode.Success
|
||||
StatusCode = SamlStatusCodes.Success
|
||||
},
|
||||
ServiceProvider = serviceProvider,
|
||||
RelayState = relayState
|
||||
|
|
@ -49,12 +48,11 @@ internal class LogoutResponseBuilder(
|
|||
|
||||
return new LogoutResponse
|
||||
{
|
||||
Id = ResponseId.New(),
|
||||
Version = SamlVersion.V2,
|
||||
Id = SamlIds.NewResponseId(),
|
||||
IssueInstant = timeProvider.GetUtcNow().UtcDateTime,
|
||||
Destination = destination.Location,
|
||||
Issuer = issuer,
|
||||
InResponseTo = request.LogoutRequest.Id.ToString(),
|
||||
InResponseTo = request.LogoutRequest.Id,
|
||||
Status = new Status
|
||||
{
|
||||
StatusCode = error.StatusCode,
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ internal record LogoutRequest : ISamlRequest
|
|||
/// <summary>
|
||||
/// Gets or sets the unique identifier for this request.
|
||||
/// </summary>
|
||||
public required RequestId Id { get; set; }
|
||||
public required string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SAML version. Must be "2.0".
|
||||
/// </summary>
|
||||
public SamlVersion Version { get; set; }
|
||||
public string Version { get; set; } = SamlVersions.V2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time instant of issue in UTC.
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ internal class LogoutResponse : EndpointResult<LogoutResponse>
|
|||
/// <summary>
|
||||
/// Gets or sets the unique identifier for this response.
|
||||
/// </summary>
|
||||
public required ResponseId Id { get; set; }
|
||||
public required string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SAML version. Must be "2.0".
|
||||
/// </summary>
|
||||
public SamlVersion Version { get; set; } = SamlVersion.V2;
|
||||
public string Version { get; set; } = SamlVersions.V2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time instant of issue in UTC.
|
||||
|
|
@ -80,7 +80,7 @@ internal class LogoutResponse : EndpointResult<LogoutResponse>
|
|||
|
||||
var encodedResponse = Convert.ToBase64String(Encoding.UTF8.GetBytes(signedResponseXml));
|
||||
|
||||
var html = HttpResponseBindings.GenerateAutoPostForm(SamlMessageName.SamlResponse, encodedResponse, result.Destination, result.RelayState);
|
||||
var html = HttpResponseBindings.GenerateAutoPostForm(SamlConstants.RequestProperties.SAMLResponse, encodedResponse, result.Destination, result.RelayState);
|
||||
|
||||
httpContext.Response.ContentType = "text/html";
|
||||
httpContext.Response.Headers.CacheControl = "no-cache, no-store";
|
||||
|
|
@ -118,8 +118,8 @@ internal class LogoutResponse : EndpointResult<LogoutResponse>
|
|||
|
||||
// Build LogoutResponse element
|
||||
var responseElement = new XElement(protocolNs + ElementNames.RootElement,
|
||||
new XAttribute("ID", toSerialize.Id.Value),
|
||||
new XAttribute("Version", toSerialize.Version.ToString()),
|
||||
new XAttribute("ID", toSerialize.Id),
|
||||
new XAttribute("Version", toSerialize.Version),
|
||||
new XAttribute("IssueInstant", issueInstant),
|
||||
new XAttribute("Destination", toSerialize.Destination),
|
||||
new XAttribute("InResponseTo", toSerialize.InResponseTo),
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ internal class SamlFrontChannelLogoutRequestBuilder(
|
|||
|
||||
var logoutRequest = new LogoutRequest
|
||||
{
|
||||
Id = RequestId.New(),
|
||||
Version = SamlVersion.V2,
|
||||
Id = SamlIds.NewRequestId(),
|
||||
IssueInstant = timeProvider.GetUtcNow().UtcDateTime,
|
||||
Destination = serviceProvider.SingleLogoutServiceUrl.Location,
|
||||
Issuer = issuer,
|
||||
|
|
@ -65,8 +64,8 @@ internal class SamlFrontChannelLogoutRequestBuilder(
|
|||
var assertionNs = XNamespace.Get(SamlConstants.Namespaces.Assertion);
|
||||
|
||||
var requestElement = new XElement(protocolNs + LogoutRequest.ElementNames.RootElement,
|
||||
new XAttribute("ID", logoutRequest.Id.Value),
|
||||
new XAttribute("Version", logoutRequest.Version.ToString()),
|
||||
new XAttribute("ID", logoutRequest.Id),
|
||||
new XAttribute("Version", logoutRequest.Version),
|
||||
new XAttribute("IssueInstant", issueInstant),
|
||||
new XAttribute("Destination", logoutRequest.Destination!),
|
||||
new XElement(assertionNs + LogoutRequest.ElementNames.Issuer, logoutRequest.Issuer));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using Duende.IdentityServer.Internal.Saml.Infrastructure;
|
||||
using Duende.IdentityServer.Internal.Saml.SingleLogout.Models;
|
||||
using Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Stores;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -63,7 +62,7 @@ internal class SamlLogoutCallbackProcessor(
|
|||
}
|
||||
|
||||
var response = await logoutResponseBuilder.BuildSuccessResponseAsync(
|
||||
new RequestId(data.SamlLogoutRequestId),
|
||||
data.SamlLogoutRequestId,
|
||||
sp,
|
||||
data.SamlRelayState);
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ internal class SamlLogoutRequestProcessor : SamlRequestProcessorBase<LogoutReque
|
|||
Type = SamlRequestErrorType.Protocol,
|
||||
ProtocolError = new SamlProtocolError<SamlLogoutRequest>(sp, request, new SamlError
|
||||
{
|
||||
StatusCode = SamlStatusCode.Requester,
|
||||
StatusCode = SamlStatusCodes.Requester,
|
||||
Message = "Logout request expired (NotOnOrAfter is in the past)"
|
||||
})
|
||||
};
|
||||
|
|
@ -163,7 +163,7 @@ internal class SamlLogoutRequestProcessor : SamlRequestProcessorBase<LogoutReque
|
|||
ClientIds = oidcClientIds,
|
||||
SamlServiceProviderEntityId = serviceProvider.EntityId,
|
||||
SamlSessions = samlSessions,
|
||||
SamlLogoutRequestId = logoutRequest.LogoutRequest.Id.Value,
|
||||
SamlLogoutRequestId = logoutRequest.LogoutRequest.Id,
|
||||
SamlRelayState = logoutRequest.RelayState,
|
||||
PostLogoutRedirectUri = _urlBuilder.SamlLogoutCallBackUri().ToString()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using Duende.IdentityServer.Services;
|
|||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SamlStatusCode = Duende.IdentityServer.Saml.Models.SamlStatusCode;
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin;
|
||||
|
||||
|
|
@ -37,7 +36,7 @@ internal class DefaultSamlSigninInteractionResponseGenerator(
|
|||
// ForceAuthn and IsPassive are "true", the identity provider MUST NOT freshly authenticate the
|
||||
//presenter unless the constraints of IsPassive can be met.
|
||||
logger.SamlInteractionPassiveAndForced(LogLevel.Debug);
|
||||
return SamlInteractionResponse.CreateError(SamlStatusCode.NoPassive, "The user is not currently logged in");
|
||||
return SamlInteractionResponse.CreateError(SamlStatusCodes.NoPassive, "The user is not currently logged in");
|
||||
}
|
||||
|
||||
if (request.ForceAuthn)
|
||||
|
|
@ -57,7 +56,7 @@ internal class DefaultSamlSigninInteractionResponseGenerator(
|
|||
if (request.IsPassive)
|
||||
{
|
||||
logger.SamlInteractionNoPassive(LogLevel.Debug);
|
||||
return SamlInteractionResponse.CreateError(SamlStatusCode.NoPassive, "The user is not currently logged in and passive login was requested.");
|
||||
return SamlInteractionResponse.CreateError(SamlStatusCodes.NoPassive, "The user is not currently logged in and passive login was requested.");
|
||||
}
|
||||
|
||||
// Todo: The AuthN request may contain hints on account creation 3.4.1.1 Element <NameIDPolicy>: AllowCreate
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ internal record Assertion
|
|||
///- xs:ID must conform to the NCName production (Non-Colonized Name) from the XML Namespaces specification
|
||||
///- NCName cannot start with a digit, colon, or certain other characters
|
||||
/// </summary>
|
||||
public AssertionId Id { get; } = AssertionId.NewId();
|
||||
public string Id { get; } = SamlIds.NewAssertionId();
|
||||
|
||||
/// <summary>
|
||||
/// SAML version (must be "2.0")
|
||||
/// </summary>
|
||||
public SamlVersion Version { get; } = SamlVersion.V2;
|
||||
public string Version { get; } = SamlVersions.V2;
|
||||
|
||||
/// <summary>
|
||||
/// Time instant of issuance
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for this assertion
|
||||
/// Must start with a _ character and be unique
|
||||
///
|
||||
/// According to SAML 2.0 Core Specification (Section 1.3.4):
|
||||
///- ID attributes must be of type xs:ID
|
||||
///- xs:ID must conform to the NCName production (Non-Colonized Name) from the XML Namespaces specification
|
||||
///- NCName cannot start with a digit, colon, or certain other characters
|
||||
/// </summary>
|
||||
internal readonly record struct AssertionId(string Value)
|
||||
{
|
||||
public static AssertionId NewId() => new("_" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public static implicit operator AssertionId(string value) => new(value);
|
||||
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for this assertion
|
||||
/// Must start with a _ character and be unique
|
||||
///
|
||||
/// According to SAML 2.0 Core Specification (Section 1.3.4):
|
||||
///- ID attributes must be of type xs:ID
|
||||
///- xs:ID must conform to the NCName production (Non-Colonized Name) from the XML Namespaces specification
|
||||
///- NCName cannot start with a digit, colon, or certain other characters
|
||||
/// </summary>
|
||||
internal readonly record struct RequestId(string Value)
|
||||
{
|
||||
public static RequestId New() => new("_" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public static implicit operator RequestId(string value) => new(value);
|
||||
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Unique identifier for this assertion
|
||||
/// Must start with a _ character and be unique
|
||||
///
|
||||
/// According to SAML 2.0 Core Specification (Section 1.3.4):
|
||||
///- ID attributes must be of type xs:ID
|
||||
///- xs:ID must conform to the NCName production (Non-Colonized Name) from the XML Namespaces specification
|
||||
///- NCName cannot start with a digit, colon, or certain other characters
|
||||
/// </summary>
|
||||
internal readonly record struct ResponseId(string Value)
|
||||
{
|
||||
public static ResponseId New() => new("_" + Guid.NewGuid().ToString("N"));
|
||||
|
||||
public static implicit operator ResponseId(string value) => new(value);
|
||||
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Generates SAML-compliant ID values.
|
||||
/// SAML 2.0 IDs must conform to xs:ID (NCName), which cannot start with a digit,
|
||||
/// so we prefix with underscore as required by the spec.
|
||||
/// </summary>
|
||||
internal static class SamlIds
|
||||
{
|
||||
internal static string NewRequestId() => "_" + Guid.NewGuid().ToString("N");
|
||||
internal static string NewResponseId() => "_" + Guid.NewGuid().ToString("N");
|
||||
internal static string NewAssertionId() => "_" + Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
|
@ -19,12 +19,12 @@ internal class SamlResponse : EndpointResult<SamlResponse>
|
|||
/// <summary>
|
||||
/// Gets or sets the unique identifier for this response.
|
||||
/// </summary>
|
||||
public ResponseId Id { get; } = ResponseId.New();
|
||||
public string Id { get; } = SamlIds.NewResponseId();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the SAML version. Must be "2.0".
|
||||
/// </summary>
|
||||
public SamlVersion Version { get; } = SamlVersion.V2;
|
||||
public string Version { get; } = SamlVersions.V2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time instant of issue in UTC.
|
||||
|
|
@ -87,7 +87,7 @@ internal class SamlResponse : EndpointResult<SamlResponse>
|
|||
|
||||
var encodedResponse = Convert.ToBase64String(Encoding.UTF8.GetBytes(signedResponseXml));
|
||||
|
||||
var html = HttpResponseBindings.GenerateAutoPostForm(SamlMessageName.SamlResponse, encodedResponse, result.Destination, result.RelayState);
|
||||
var html = HttpResponseBindings.GenerateAutoPostForm(SamlConstants.RequestProperties.SAMLResponse, encodedResponse, result.Destination, result.RelayState);
|
||||
|
||||
httpContext.Response.ContentType = "text/html";
|
||||
httpContext.Response.Headers.CacheControl = "no-cache, no-store";
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
|
||||
internal readonly record struct SessionId(Guid Value)
|
||||
{
|
||||
public static SessionId NewId() => new(Guid.NewGuid());
|
||||
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
// See LICENSE in the project root for license information.
|
||||
|
||||
#nullable enable
|
||||
using Duende.IdentityServer.Saml.Models;
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ internal record Status
|
|||
/// <summary>
|
||||
/// Gets or sets the status code indicating the success or failure of the request.
|
||||
/// </summary>
|
||||
public required SamlStatusCode StatusCode { get; set; }
|
||||
public required string StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional human-readable message providing additional information about the status.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ using Duende.IdentityServer.Services;
|
|||
using Duende.IdentityServer.Stores;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SamlStatusCode = Duende.IdentityServer.Saml.Models.SamlStatusCode;
|
||||
|
||||
namespace Duende.IdentityServer.Internal.Saml.SingleSignin;
|
||||
|
||||
|
|
@ -157,8 +156,8 @@ internal class SamlSigninRequestProcessor(
|
|||
|
||||
var samlError = new SamlError
|
||||
{
|
||||
StatusCode = SamlStatusCode.Responder,
|
||||
SubStatusCode = SamlStatusCode.InvalidNameIdPolicy,
|
||||
StatusCode = SamlStatusCodes.Responder,
|
||||
SubStatusCode = SamlStatusCodes.InvalidNameIdPolicy,
|
||||
Message = $"Requested NameID format '{requestedFormat}' is not supported by this IdP"
|
||||
};
|
||||
return new SamlRequestError<SamlSigninRequest>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public record AuthNRequest : ISamlRequest
|
|||
/// <summary>
|
||||
/// Gets or sets the SAML version. Must be "2.0".
|
||||
/// </summary>
|
||||
public required SamlVersion Version { get; set; }
|
||||
public required string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time instant of issue in UTC.
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Saml.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a SAML 2.0 status code as defined in the SAML 2.0 Core specification.
|
||||
/// </summary>
|
||||
public readonly record struct SamlStatusCode(string Value)
|
||||
{
|
||||
public static readonly SamlStatusCode Success = new("urn:oasis:names:tc:SAML:2.0:status:Success");
|
||||
public static readonly SamlStatusCode Requester = new("urn:oasis:names:tc:SAML:2.0:status:Requester");
|
||||
public static readonly SamlStatusCode Responder = new("urn:oasis:names:tc:SAML:2.0:status:Responder");
|
||||
public static readonly SamlStatusCode VersionMismatch = new("urn:oasis:names:tc:SAML:2.0:status:VersionMismatch");
|
||||
public static readonly SamlStatusCode NoAuthnContext = new("urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext");
|
||||
public static readonly SamlStatusCode AuthnFailed = new("urn:oasis:names:tc:SAML:2.0:status:AuthnFailed");
|
||||
public static readonly SamlStatusCode InvalidNameIdPolicy = new("urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy");
|
||||
public static readonly SamlStatusCode RequestDenied = new("urn:oasis:names:tc:SAML:2.0:status:RequestDenied");
|
||||
public static readonly SamlStatusCode UnknownPrincipal = new("urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal");
|
||||
public static readonly SamlStatusCode UnsupportedBinding = new("urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding");
|
||||
public static readonly SamlStatusCode NoPassive = new("urn:oasis:names:tc:SAML:2.0:status:NoPassive");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Value;
|
||||
|
||||
public static implicit operator string(SamlStatusCode statusCode) => statusCode.Value;
|
||||
|
||||
public static implicit operator SamlStatusCode(string value) => new(value);
|
||||
|
||||
public SamlStatusCode ToSamlStatusCode() => Value;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Saml.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Well-known SAML 2.0 status code URNs as defined in the SAML 2.0 Core specification.
|
||||
/// </summary>
|
||||
public static class SamlStatusCodes
|
||||
{
|
||||
/// <summary>The request succeeded.</summary>
|
||||
public const string Success = "urn:oasis:names:tc:SAML:2.0:status:Success";
|
||||
|
||||
/// <summary>The request could not be performed due to an error on the part of the requester.</summary>
|
||||
public const string Requester = "urn:oasis:names:tc:SAML:2.0:status:Requester";
|
||||
|
||||
/// <summary>The request could not be performed due to an error on the part of the SAML responder or SAML authority.</summary>
|
||||
public const string Responder = "urn:oasis:names:tc:SAML:2.0:status:Responder";
|
||||
|
||||
/// <summary>The SAML responder could not process the request because the version of the request message was incorrect.</summary>
|
||||
public const string VersionMismatch = "urn:oasis:names:tc:SAML:2.0:status:VersionMismatch";
|
||||
|
||||
/// <summary>The responding provider cannot authenticate the principal by means of the currently deployed authentication authority.</summary>
|
||||
public const string NoAuthnContext = "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext";
|
||||
|
||||
/// <summary>The authentication attempt failed.</summary>
|
||||
public const string AuthnFailed = "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed";
|
||||
|
||||
/// <summary>The responding provider cannot permit a subject confirmation based on the requirements of the requester.</summary>
|
||||
public const string InvalidNameIdPolicy = "urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy";
|
||||
|
||||
/// <summary>The SAML responder or SAML authority is able to process the request but has chosen not to respond.</summary>
|
||||
public const string RequestDenied = "urn:oasis:names:tc:SAML:2.0:status:RequestDenied";
|
||||
|
||||
/// <summary>The responding provider does not recognize the principal specified or implied by the request.</summary>
|
||||
public const string UnknownPrincipal = "urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal";
|
||||
|
||||
/// <summary>The SAML responder cannot properly fulfill the request using the protocol binding specified in the request.</summary>
|
||||
public const string UnsupportedBinding = "urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding";
|
||||
|
||||
/// <summary>The identity provider cannot authenticate the presenter in a manner that satisfies the IsPassive constraint of the request.</summary>
|
||||
public const string NoPassive = "urn:oasis:names:tc:SAML:2.0:status:NoPassive";
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Saml.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a SAML version string.
|
||||
/// </summary>
|
||||
public readonly record struct SamlVersion(string Value)
|
||||
{
|
||||
public static readonly SamlVersion V2 = new("2.0");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => Value;
|
||||
|
||||
public static implicit operator SamlVersion(string value) => new(value);
|
||||
|
||||
public SamlVersion ToSamlVersion() => Value;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Duende Software. All rights reserved.
|
||||
// See LICENSE in the project root for license information.
|
||||
|
||||
namespace Duende.IdentityServer.Saml.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Well-known SAML version strings.
|
||||
/// </summary>
|
||||
public static class SamlVersions
|
||||
{
|
||||
/// <summary>SAML version 2.0.</summary>
|
||||
public const string V2 = "2.0";
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using Duende.IdentityModel;
|
||||
using Duende.IdentityServer.Models;
|
||||
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
|
||||
using SamlStatusCode = Duende.IdentityServer.Saml.Models.SamlStatusCode;
|
||||
using Duende.IdentityServer.Saml.Models;
|
||||
|
||||
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ public class SamlEncryptionTests
|
|||
|
||||
var samlResponse = await ExtractAndDecryptSamlSuccessFromPostAsync(result, encryptionCert, CancellationToken.None);
|
||||
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
samlResponse.Assertion.ShouldNotBeNull();
|
||||
|
||||
samlResponse.Assertion.Subject.ShouldNotBeNull();
|
||||
|
|
@ -453,7 +453,7 @@ public class SamlEncryptionTests
|
|||
|
||||
// Verify can parse as success
|
||||
var samlResponse = await ExtractSamlSuccessFromPostAsync(result, CancellationToken.None);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
samlResponse.Assertion.ShouldNotBeNull();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ using Duende.IdentityServer.Models;
|
|||
using Duende.IdentityServer.Saml.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Duende.IdentityServer.IntegrationTests.Endpoints.Saml.SamlTestHelpers;
|
||||
using SamlStatusCode = Duende.IdentityServer.Saml.Models.SamlStatusCode;
|
||||
|
||||
namespace Duende.IdentityServer.IntegrationTests.Endpoints.Saml;
|
||||
|
||||
|
|
@ -128,7 +127,7 @@ public class SamlSigninEndpointTests
|
|||
await Fixture.Client.GetAsync($"/saml/signin?SAMLRequest={urlEncoded}&RelayState={Data.RelayState}", _ct);
|
||||
|
||||
var samlSuccessResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
samlSuccessResponse.RelayState.ShouldBe(Data.RelayState);
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +250,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.PostAsync($"/saml/signin?", content, _ct);
|
||||
|
||||
var samlSuccessResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
samlSuccessResponse.RelayState.ShouldBe(Data.RelayState);
|
||||
}
|
||||
|
||||
|
|
@ -542,7 +541,7 @@ public class SamlSigninEndpointTests
|
|||
successResponse.Issuer.ShouldBe(Fixture.Url());
|
||||
successResponse.InResponseTo.ShouldBe(Data.RequestId);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
|
||||
var assertion = successResponse.Assertion;
|
||||
assertion.ShouldNotBeNull();
|
||||
|
|
@ -836,7 +835,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
result.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -859,7 +858,7 @@ public class SamlSigninEndpointTests
|
|||
// Without ForceAuthn, authenticated user goes directly to callback
|
||||
normalResult.StatusCode.ShouldBe(HttpStatusCode.OK);
|
||||
var samlSuccessResponse = await ExtractSamlSuccessFromPostAsync(normalResult, _ct);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
|
||||
var forceAuthnRequestXml = Build.AuthNRequestXml(forceAuthn: true);
|
||||
var forceAuthnUrlEncoded = await EncodeRequest(forceAuthnRequestXml);
|
||||
|
|
@ -1038,7 +1037,7 @@ public class SamlSigninEndpointTests
|
|||
var signinCallbackResponse = await Fixture.Client.GetAsync(returnUrl, _ct);
|
||||
|
||||
var samlSuccessResponse = await ExtractSamlSuccessFromPostAsync(signinCallbackResponse, _ct);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlSuccessResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
samlSuccessResponse.Destination.ShouldBe(primaryAcsUrl.ToString());
|
||||
}
|
||||
|
||||
|
|
@ -1151,7 +1150,7 @@ public class SamlSigninEndpointTests
|
|||
var (_, _, responseElement) = ParseSamlResponseXml(responseXml);
|
||||
VerifySignaturePositionAfterIssuer(responseElement);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1183,7 +1182,7 @@ public class SamlSigninEndpointTests
|
|||
assertionElement.ShouldNotBeNull();
|
||||
VerifySignaturePositionAfterIssuer(assertionElement!);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1217,7 +1216,7 @@ public class SamlSigninEndpointTests
|
|||
assertionElement.ShouldNotBeNull();
|
||||
VerifySignaturePositionAfterIssuer(assertionElement!);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1244,7 +1243,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
VerifySignaturePresence(responseXml, expectResponseSignature: false, expectAssertionSignature: false);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1272,7 +1271,7 @@ public class SamlSigninEndpointTests
|
|||
// Default behavior should be SignAssertion per SAML best practices
|
||||
VerifySignaturePresence(responseXml, expectResponseSignature: false, expectAssertionSignature: true);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1564,7 +1563,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
successResponse.RelayState.ShouldBe(relayStateValue);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1585,7 +1584,7 @@ public class SamlSigninEndpointTests
|
|||
var normalUrlEncoded = await EncodeRequest(normalRequestXml);
|
||||
var normalResult = await Fixture.Client.GetAsync($"/saml/signin?SAMLRequest={normalUrlEncoded}", _ct);
|
||||
var normalResponse = await ExtractSamlSuccessFromPostAsync(normalResult, _ct);
|
||||
normalResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
normalResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
|
||||
var forceAuthnRequestXml = Build.AuthNRequestXml(forceAuthn: true);
|
||||
var forceAuthnUrlEncoded = await EncodeRequest(forceAuthnRequestXml);
|
||||
|
|
@ -1624,7 +1623,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
VerifySignaturePresence(responseXml, expectResponseSignature: true, expectAssertionSignature: true);
|
||||
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1656,7 +1655,7 @@ public class SamlSigninEndpointTests
|
|||
VerifySignaturePresence(responseXml, expectResponseSignature: true, expectAssertionSignature: false);
|
||||
|
||||
successResponse.Destination.ShouldBe(secondaryAcsUrl.ToString());
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
||||
|
|
@ -1673,7 +1672,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.GetAsync($"/saml/signin?SAMLRequest={urlEncoded}", _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Missing signature parameter");
|
||||
}
|
||||
|
||||
|
|
@ -1693,7 +1692,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.PostAsync("/saml/signin", content, _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Signature element not found");
|
||||
}
|
||||
|
||||
|
|
@ -1714,7 +1713,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Invalid signature");
|
||||
}
|
||||
|
||||
|
|
@ -1736,7 +1735,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.PostAsync("/saml/signin", content, _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Invalid signature");
|
||||
}
|
||||
|
||||
|
|
@ -1778,7 +1777,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1802,7 +1801,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.PostAsync("/saml/signin", content, _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1821,7 +1820,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.GetAsync($"/saml/signin?SAMLRequest={urlEncoded}", _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1844,7 +1843,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1883,7 +1882,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1904,7 +1903,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Unsupported signature algorithm: http://www.w3.org/2000/09/xmldsig#rsa-sha1");
|
||||
}
|
||||
|
||||
|
|
@ -1926,7 +1925,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&SigAlg={Uri.EscapeDataString(sigAlg)}&Signature={Uri.EscapeDataString(signature)}", _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Invalid signature");
|
||||
}
|
||||
|
||||
|
|
@ -1951,7 +1950,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&RelayState={Uri.EscapeDataString(relayState)}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.RelayState.ShouldBe(relayState);
|
||||
}
|
||||
|
||||
|
|
@ -1973,7 +1972,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.PostAsync("/saml/signin", content, _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Invalid signature");
|
||||
}
|
||||
|
||||
|
|
@ -1996,7 +1995,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.PostAsync("/saml/signin", content, _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
samlError.StatusMessage.ShouldBe("Invalid signature");
|
||||
}
|
||||
|
||||
|
|
@ -2022,7 +2021,7 @@ public class SamlSigninEndpointTests
|
|||
var result = await Fixture.Client.PostAsync("/saml/signin", content, _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -2041,7 +2040,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Responder.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Responder);
|
||||
samlError.StatusMessage.ShouldBe("No valid certificates configured for service provider");
|
||||
}
|
||||
|
||||
|
|
@ -2061,7 +2060,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var samlError = await ExtractSamlErrorFromPostAsync(result);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCode.Responder.Value);
|
||||
samlError.StatusCode.ShouldBe(SamlStatusCodes.Responder);
|
||||
samlError.StatusMessage.ShouldBe("No valid certificates configured for service provider");
|
||||
}
|
||||
|
||||
|
|
@ -2085,7 +2084,7 @@ public class SamlSigninEndpointTests
|
|||
$"/saml/signin?SAMLRequest={urlEncoded}&Signature={Uri.EscapeDataString(signature)}&SigAlg={Uri.EscapeDataString(sigAlg)}", _ct);
|
||||
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -2124,7 +2123,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.AuthnStatement?.AuthnContextClassRef.ShouldBe("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
|
||||
}
|
||||
|
||||
|
|
@ -2164,8 +2163,8 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var samlResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlResponse.SubStatusCode.ShouldBe(SamlStatusCode.NoAuthnContext.Value);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
samlResponse.SubStatusCode.ShouldBe(SamlStatusCodes.NoAuthnContext);
|
||||
samlResponse.Assertion.AuthnStatement?.AuthnContextClassRef.ShouldBe("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");
|
||||
}
|
||||
|
||||
|
|
@ -2195,8 +2194,8 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var samlResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlResponse.SubStatusCode.ShouldBe(SamlStatusCode.NoAuthnContext.Value);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
samlResponse.SubStatusCode.ShouldBe(SamlStatusCodes.NoAuthnContext);
|
||||
samlResponse.Assertion.AuthnStatement?.AuthnContextClassRef.ShouldBe("urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified");
|
||||
}
|
||||
|
||||
|
|
@ -2224,7 +2223,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.AuthnStatement?.AuthnContextClassRef.ShouldBe("urn:oasis:names:tc:SAML:2.0:ac:classes:X509");
|
||||
}
|
||||
|
||||
|
|
@ -2299,8 +2298,8 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var errorResponse = await ExtractSamlErrorFromPostAsync(result);
|
||||
errorResponse.StatusCode.ShouldBe(SamlStatusCode.Responder.Value);
|
||||
errorResponse.SubStatusCode.ShouldBe(SamlStatusCode.InvalidNameIdPolicy.Value);
|
||||
errorResponse.StatusCode.ShouldBe(SamlStatusCodes.Responder);
|
||||
errorResponse.SubStatusCode.ShouldBe(SamlStatusCodes.InvalidNameIdPolicy);
|
||||
errorResponse.StatusMessage.ShouldBe($"Requested NameID format '{unsupportedFormat}' is not supported by this IdP");
|
||||
}
|
||||
|
||||
|
|
@ -2367,7 +2366,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("test@test.com");
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.EmailAddress);
|
||||
}
|
||||
|
|
@ -2395,7 +2394,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe(persistentIdentifier);
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.Persistent);
|
||||
}
|
||||
|
|
@ -2425,7 +2424,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.Unspecified);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("user-id");
|
||||
}
|
||||
|
|
@ -2456,8 +2455,8 @@ public class SamlSigninEndpointTests
|
|||
var nameId2 = response2.Assertion.Subject?.NameId;
|
||||
|
||||
// Verify both responses succeeded
|
||||
response1.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
response2.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
response1.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
response2.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
|
||||
// Verify format is transient
|
||||
response1.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.Transient);
|
||||
|
|
@ -2494,7 +2493,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe(persistentId);
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.Persistent);
|
||||
successResponse.Assertion.Subject?.SPNameQualifier.ShouldBe(Data.EntityId.ToString());
|
||||
|
|
@ -2527,7 +2526,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe(spSpecificId); // Uses SP override, not default
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.Persistent);
|
||||
successResponse.Assertion.Subject?.SPNameQualifier.ShouldBe(Data.EntityId.ToString());
|
||||
|
|
@ -2612,7 +2611,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.SPNameQualifier.ShouldBe(Data.EntityId.ToString());
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.Persistent);
|
||||
}
|
||||
|
|
@ -2648,7 +2647,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe(customPersistentId);
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.Persistent);
|
||||
}
|
||||
|
|
@ -2689,8 +2688,8 @@ public class SamlSigninEndpointTests
|
|||
var responseB = await ExtractSamlSuccessFromPostAsync(resultB, _ct);
|
||||
|
||||
// Assert
|
||||
responseA.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
responseB.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
responseA.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
responseB.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
|
||||
responseA.Assertion.Subject?.NameId.ShouldBe(userAPersistentId);
|
||||
responseB.Assertion.Subject?.NameId.ShouldBe(userBPersistentId);
|
||||
|
|
@ -2778,7 +2777,7 @@ public class SamlSigninEndpointTests
|
|||
|
||||
// Assert
|
||||
var successResponse = await ExtractSamlSuccessFromPostAsync(result, _ct);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
successResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
successResponse.Assertion.Subject?.NameId.ShouldBe("test@testing.com");
|
||||
successResponse.Assertion.Subject?.NameIdFormat.ShouldBe(SamlConstants.NameIdentifierFormats.EmailAddress);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ public class SamlSingleLogoutCallbackEndpointTests
|
|||
|
||||
// Assert
|
||||
var samlResponse = await SamlTestHelpers.ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
samlResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.VersionMismatch.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.VersionMismatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -209,7 +209,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
logoutResponse.StatusMessage.ShouldBe("Request IssueInstant is in the future");
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +235,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
logoutResponse.StatusMessage.ShouldBe("Request has expired (IssueInstant too old)");
|
||||
}
|
||||
|
||||
|
|
@ -259,7 +259,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
logoutResponse.StatusMessage.ShouldBe($"Invalid destination. Expected '{Fixture.Url()}/saml/logout'");
|
||||
}
|
||||
|
||||
|
|
@ -309,7 +309,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
logoutResponse.StatusMessage.ShouldBe("Missing signature parameter");
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +334,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Requester.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Requester);
|
||||
logoutResponse.StatusMessage.ShouldBe("Logout request expired (NotOnOrAfter is in the past)");
|
||||
}
|
||||
|
||||
|
|
@ -360,7 +360,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -392,7 +392,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -421,7 +421,7 @@ public class SamlSingleLogoutEndpointTests
|
|||
|
||||
// Assert
|
||||
var logoutResponse = await ExtractSamlLogoutResponseFromPostAsync(result, CancellationToken.None);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCode.Success.Value);
|
||||
logoutResponse.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class LogoutRequestParserTests
|
|||
var result = _parser.Parse(doc);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.Id.Value.ShouldBe("_test-logout-id");
|
||||
result.Id.ShouldBe("_test-logout-id");
|
||||
result.Issuer.ShouldBe("https://sp.example.com");
|
||||
result.Destination!.ToString().ShouldBe("https://idp.example.com/saml/logout");
|
||||
result.NameId.Value.ShouldBe("user@example.com");
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ public class SamlLogoutCallbackProcessorTests
|
|||
var logoutResponse = result.Value;
|
||||
logoutResponse.InResponseTo.ShouldBe("_request123");
|
||||
logoutResponse.Destination.ShouldBe(sp.SingleLogoutServiceUrl!.Location);
|
||||
logoutResponse.Status.StatusCode.ShouldBe(SamlStatusCode.Success);
|
||||
logoutResponse.Status.StatusCode.ShouldBe(SamlStatusCodes.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public class SamlProtocolMessageSignerTests
|
|||
new XElement(assertionNs + "Issuer", "https://idp.example.com"),
|
||||
new XElement(protocolNs + "Status",
|
||||
new XElement(protocolNs + "StatusCode",
|
||||
new XAttribute("Value", SamlStatusCode.Success.ToString()))));
|
||||
new XAttribute("Value", SamlStatusCodes.Success))));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ using Duende.IdentityServer.Internal.Saml;
|
|||
using Duende.IdentityServer.Internal.Saml.Infrastructure;
|
||||
using Duende.IdentityServer.Internal.Saml.SingleSignin.Models;
|
||||
using Duende.IdentityServer.Models;
|
||||
using SamlStatusCode = Duende.IdentityServer.Saml.Models.SamlStatusCode;
|
||||
using Duende.IdentityServer.Saml.Models;
|
||||
|
||||
namespace UnitTests.Saml;
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ public class XmlSignatureHelperTests
|
|||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status
|
||||
{
|
||||
StatusCode = SamlStatusCode.Success
|
||||
StatusCode = SamlStatusCodes.Success
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ public class XmlSignatureHelperTests
|
|||
IssueInstant = DateTime.UtcNow,
|
||||
Issuer = "https://idp.example.com",
|
||||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status { StatusCode = SamlStatusCode.Success },
|
||||
Status = new Status { StatusCode = SamlStatusCodes.Success },
|
||||
Assertion = new Assertion
|
||||
{
|
||||
IssueInstant = DateTime.UtcNow,
|
||||
|
|
@ -131,7 +131,7 @@ public class XmlSignatureHelperTests
|
|||
IssueInstant = DateTime.UtcNow,
|
||||
Issuer = "https://idp.example.com",
|
||||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status { StatusCode = SamlStatusCode.Success },
|
||||
Status = new Status { StatusCode = SamlStatusCodes.Success },
|
||||
Assertion = new Assertion
|
||||
{
|
||||
IssueInstant = DateTime.UtcNow,
|
||||
|
|
@ -171,7 +171,7 @@ public class XmlSignatureHelperTests
|
|||
IssueInstant = DateTime.UtcNow,
|
||||
Issuer = "https://idp.example.com",
|
||||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status { StatusCode = SamlStatusCode.Success }
|
||||
Status = new Status { StatusCode = SamlStatusCodes.Success }
|
||||
};
|
||||
|
||||
var responseElement = _responseSerializer.Serialize(response);
|
||||
|
|
@ -236,7 +236,7 @@ public class XmlSignatureHelperTests
|
|||
IssueInstant = DateTime.UtcNow,
|
||||
Issuer = "https://idp.example.com",
|
||||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status { StatusCode = SamlStatusCode.Success }
|
||||
Status = new Status { StatusCode = SamlStatusCodes.Success }
|
||||
};
|
||||
|
||||
var responseElement = _responseSerializer.Serialize(response);
|
||||
|
|
@ -258,7 +258,7 @@ public class XmlSignatureHelperTests
|
|||
IssueInstant = DateTime.UtcNow,
|
||||
Issuer = "https://idp.example.com",
|
||||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status { StatusCode = SamlStatusCode.Success }
|
||||
Status = new Status { StatusCode = SamlStatusCodes.Success }
|
||||
};
|
||||
|
||||
var responseElement = _responseSerializer.Serialize(response);
|
||||
|
|
@ -313,7 +313,7 @@ public class XmlSignatureHelperTests
|
|||
IssueInstant = DateTime.UtcNow,
|
||||
Issuer = "https://idp.example.com",
|
||||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status { StatusCode = SamlStatusCode.Success }
|
||||
Status = new Status { StatusCode = SamlStatusCodes.Success }
|
||||
};
|
||||
|
||||
var responseElement = _responseSerializer.Serialize(response);
|
||||
|
|
@ -332,7 +332,7 @@ public class XmlSignatureHelperTests
|
|||
IssueInstant = DateTime.UtcNow,
|
||||
Issuer = "https://idp.example.com",
|
||||
Destination = new Uri("https://sp.example.com/acs"),
|
||||
Status = new Status { StatusCode = SamlStatusCode.Success }
|
||||
Status = new Status { StatusCode = SamlStatusCodes.Success }
|
||||
// No Assertion!
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue