diff --git a/src/Abc.IdentityServer.EidasLight/Abc.DuendeIdentityServer.EidasLight.csproj b/src/Abc.IdentityServer.EidasLight/Abc.DuendeIdentityServer.EidasLight.csproj index be95dc0..d40e321 100644 --- a/src/Abc.IdentityServer.EidasLight/Abc.DuendeIdentityServer.EidasLight.csproj +++ b/src/Abc.IdentityServer.EidasLight/Abc.DuendeIdentityServer.EidasLight.csproj @@ -6,7 +6,7 @@ - net6.0 + net6.0;net8.0 true true $(DefineConstants);DUENDE @@ -17,10 +17,21 @@ $(PackageTags);duende + + + + + + + - - + + + + + + diff --git a/src/Abc.IdentityServer.EidasLight/Abc.IdentityServer4.EidasLight.csproj b/src/Abc.IdentityServer.EidasLight/Abc.IdentityServer4.EidasLight.csproj index 20f0a78..c05ab43 100644 --- a/src/Abc.IdentityServer.EidasLight/Abc.IdentityServer4.EidasLight.csproj +++ b/src/Abc.IdentityServer.EidasLight/Abc.IdentityServer4.EidasLight.csproj @@ -10,6 +10,7 @@ true true 10.0 + $(DefineConstants);IDS4 diff --git a/src/Abc.IdentityServer.EidasLight/Endpoints/Results/ErrorPageResult.cs b/src/Abc.IdentityServer.EidasLight/Endpoints/Results/ErrorPageResult.cs index e136789..030f93d 100644 --- a/src/Abc.IdentityServer.EidasLight/Endpoints/Results/ErrorPageResult.cs +++ b/src/Abc.IdentityServer.EidasLight/Endpoints/Results/ErrorPageResult.cs @@ -19,7 +19,8 @@ internal class ErrorPageResult : IEndpointResult { private IMessageStore _errorMessageStore; private IdentityServerOptions _options; - private ISystemClock _clock; + private IServerUrls _urls; + private IClock _clock; public ErrorPageResult(string error, string errorDescription) { @@ -27,11 +28,12 @@ public ErrorPageResult(string error, string errorDescription) ErrorDescription = errorDescription; } - internal ErrorPageResult(string error, string errorDescription, IdentityServerOptions options, ISystemClock clock, IMessageStore errorMessageStore) + internal ErrorPageResult(string error, string errorDescription, IdentityServerOptions options, IClock clock, IServerUrls urls, IMessageStore errorMessageStore) : this(error, errorDescription) { _options = options; _clock = clock; + _urls = urls; _errorMessageStore = errorMessageStore; } @@ -56,14 +58,15 @@ public async Task ExecuteAsync(HttpContext context) var redirectUrl = _options.UserInteraction.ErrorUrl; redirectUrl = redirectUrl.AddQueryString(_options.UserInteraction.ErrorIdParameter, id); - context.Response.RedirectToAbsoluteUrl(redirectUrl); + context.Response.Redirect(_urls.GetAbsoluteUrl(redirectUrl)); } private void Init(HttpContext context) { _errorMessageStore ??= context.RequestServices.GetRequiredService>(); _options ??= context.RequestServices.GetRequiredService(); - _clock ??= context.RequestServices.GetRequiredService(); + _urls ??= context.RequestServices.GetRequiredService(); + _clock ??= context.RequestServices.GetRequiredService(); } } } \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/Endpoints/Results/LoginPageResult.cs b/src/Abc.IdentityServer.EidasLight/Endpoints/Results/LoginPageResult.cs index 7214c55..4a818e3 100644 --- a/src/Abc.IdentityServer.EidasLight/Endpoints/Results/LoginPageResult.cs +++ b/src/Abc.IdentityServer.EidasLight/Endpoints/Results/LoginPageResult.cs @@ -10,7 +10,6 @@ using Abc.IdentityModel.Protocols.EidasLight; using Abc.IdentityServer.EidasLight.Validation; using Abc.IdentityServer.Extensions; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using System; @@ -29,7 +28,8 @@ public class LoginPageResult : IEndpointResult private IdentityServerOptions _options; private IAuthorizationParametersMessageStore _authorizationParametersMessageStore; private EidasLightProtocolSerializer _protocolSerializer; - private ISystemClock _clock; + private IServerUrls _urls; + private IClock _clock; /// /// Initializes a new instance of the class. @@ -41,11 +41,12 @@ public LoginPageResult(ValidatedEidasLightRequest request) _request = request ?? throw new ArgumentNullException(nameof(request)); } - internal LoginPageResult(ValidatedEidasLightRequest request, IdentityServerOptions options, ISystemClock clock, EidasLightProtocolSerializer protocolSerializer, IAuthorizationParametersMessageStore authorizationParametersMessageStore) + internal LoginPageResult(ValidatedEidasLightRequest request, IdentityServerOptions options, IClock clock, IServerUrls urls, EidasLightProtocolSerializer protocolSerializer, IAuthorizationParametersMessageStore authorizationParametersMessageStore) : this(request) { _options = options; _clock = clock; + _urls = urls; _protocolSerializer = protocolSerializer; _authorizationParametersMessageStore = authorizationParametersMessageStore; } @@ -55,7 +56,7 @@ public async Task ExecuteAsync(HttpContext context) { Init(context); - var returnUrl = context.GetIdentityServerBasePath().EnsureTrailingSlash() + Constants.ProtocolRoutePaths.EidasLightProxyCallback; + var returnUrl = _urls.BasePath.EnsureTrailingSlash() + Constants.ProtocolRoutePaths.EidasLightProxyCallback; var data = _protocolSerializer.WriteMessageDictionary(_request.Message); var msg = new Message>(data, _clock.UtcNow.UtcDateTime); @@ -67,11 +68,11 @@ public async Task ExecuteAsync(HttpContext context) { // this converts the relative redirect path to an absolute one if we're // redirecting to a different server - returnUrl = context.GetIdentityServerHost().EnsureTrailingSlash() + returnUrl.RemoveLeadingSlash(); + returnUrl = _urls.Origin.EnsureTrailingSlash() + returnUrl.RemoveLeadingSlash(); } var url = loginUrl.AddQueryString(_options.UserInteraction.LoginReturnUrlParameter, returnUrl); - context.Response.RedirectToAbsoluteUrl(url); + context.Response.Redirect(_urls.GetAbsoluteUrl(url)); } private void Init(HttpContext context) @@ -79,7 +80,8 @@ private void Init(HttpContext context) _options ??= context.RequestServices.GetRequiredService(); _protocolSerializer ??= context.RequestServices.GetRequiredService(); _authorizationParametersMessageStore ??= context.RequestServices.GetRequiredService(); - _clock ??= context.RequestServices.GetRequiredService(); + _urls ??= context.RequestServices.GetRequiredService(); + _clock ??= context.RequestServices.GetRequiredService(); } } } \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/Extensions/ServerUrlExtensions.cs b/src/Abc.IdentityServer.EidasLight/Extensions/ServerUrlExtensions.cs new file mode 100644 index 0000000..d6aa6a0 --- /dev/null +++ b/src/Abc.IdentityServer.EidasLight/Extensions/ServerUrlExtensions.cs @@ -0,0 +1,65 @@ +#if IDS4 + +using Abc.IdentityServer.Extensions; +using Microsoft.AspNetCore.Http; +using System; +using System.Linq; + +namespace IdentityServer4.Extensions; + +/// +/// Extension methods for . +/// +public static class ServerUrlExtensions +{ + /// + /// Returns the origin in unicode, and not in punycode (if we have a unicode hostname). + /// + public static string GetUnicodeOrigin(this IServerUrls urls) + { + var split = urls.Origin.Split(new[] { "://" }, StringSplitOptions.RemoveEmptyEntries); + var scheme = split.First(); + var host = HostString.FromUriComponent(split.Last()).Value; + + return scheme + "://" + host; + } + + /// + /// Returns an absolute URL for the URL or path. + /// + public static string GetAbsoluteUrl(this IServerUrls urls, string urlOrPath) + { + if (urlOrPath.IsLocalUrl()) + { + if (urlOrPath.StartsWith("~/")) + { + urlOrPath = urlOrPath.Substring(1); + } + + urlOrPath = urls.BaseUrl.EnsureTrailingSlash() + urlOrPath.RemoveLeadingSlash(); + } + + return urlOrPath; + } + + /// + /// Returns the URL into the server based on the relative path. The path parameter can start with "~/" or "/". + /// + public static string GetIdentityServerRelativeUrl(this IServerUrls urls, string path) + { + if (!path.IsLocalUrl()) + { + return null; + } + + if (path.StartsWith("~/")) + { + path = path.Substring(1); + } + + path = urls.BaseUrl.EnsureTrailingSlash() + path.RemoveLeadingSlash(); + return path; + } +} + +#endif \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/Extensions/StringExtensions.cs b/src/Abc.IdentityServer.EidasLight/Extensions/StringExtensions.cs index afd2753..b7808b6 100644 --- a/src/Abc.IdentityServer.EidasLight/Extensions/StringExtensions.cs +++ b/src/Abc.IdentityServer.EidasLight/Extensions/StringExtensions.cs @@ -155,5 +155,16 @@ public static string RemoveLeadingSlash(this string url) return url; } + + [DebuggerStepThrough] + public static string RemoveTrailingSlash(this string url) + { + if (url != null && url.EndsWith('/')) + { + url = url.Substring(0, url.Length - 1); + } + + return url; + } } } \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/GlobalUsings.cs b/src/Abc.IdentityServer.EidasLight/GlobalUsings.cs index 86832c4..75a6b80 100644 --- a/src/Abc.IdentityServer.EidasLight/GlobalUsings.cs +++ b/src/Abc.IdentityServer.EidasLight/GlobalUsings.cs @@ -23,3 +23,9 @@ global using Ids = IdentityServer4; global using StatusCodeResult = IdentityServer4.Endpoints.Results.StatusCodeResult; #endif + +#if NET8_0_OR_GREATER +global using IClock = Duende.IdentityServer.IClock; +#else +global using IClock = Microsoft.AspNetCore.Authentication.ISystemClock; +#endif \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInInteractionResponseGenerator.cs b/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInInteractionResponseGenerator.cs index 336cde4..c4b42c4 100644 --- a/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInInteractionResponseGenerator.cs +++ b/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInInteractionResponseGenerator.cs @@ -8,7 +8,6 @@ // ---------------------------------------------------------------------------- using Abc.IdentityServer.EidasLight.Validation; -using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using System; using System.Linq; @@ -31,7 +30,7 @@ public class SignInInteractionResponseGenerator : ISignInInteractionResponseGene /// /// The clock. /// - protected readonly ISystemClock Clock; + protected readonly IClock Clock; #pragma warning restore SA1401 // Fields should be private /// @@ -39,7 +38,7 @@ public class SignInInteractionResponseGenerator : ISignInInteractionResponseGene /// /// The clock. /// The logger. - public SignInInteractionResponseGenerator(ISystemClock clock, ILogger logger) + public SignInInteractionResponseGenerator(IClock clock, ILogger logger) { Clock = clock; Logger = logger; diff --git a/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInResponseGenerator.cs b/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInResponseGenerator.cs index b5a4c4e..b8ab59a 100644 --- a/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInResponseGenerator.cs +++ b/src/Abc.IdentityServer.EidasLight/ResponseProcessing/SignInResponseGenerator.cs @@ -28,6 +28,7 @@ public class SignInResponseGenerator : ISignInResponseGenerator private readonly IHttpContextAccessor _contextAccessor; private readonly Services.IClaimsService _claims; private readonly IResourceStore _resources; + private readonly IIssuerNameService _issuerNameService; private readonly ILogger _logger; public SignInResponseGenerator( @@ -35,12 +36,14 @@ public SignInResponseGenerator( EidasLightOptions options, Services.IClaimsService claimsService, IResourceStore resources, + IIssuerNameService issuerNameService, ILogger logger) { _contextAccessor = contextAccessor; _options = options; _claims = claimsService; _resources = resources; + _issuerNameService = issuerNameService; _logger = logger; } @@ -117,7 +120,7 @@ protected virtual async Task CreateSubjectAsync(SignInValidation return new ClaimsIdentity(outboundClaims, "idsrv"); } - private Task CreateResponseAsync(ValidatedEidasLightRequest validatedRequest, ClaimsIdentity outgoingSubject) + private async Task CreateResponseAsync(ValidatedEidasLightRequest validatedRequest, ClaimsIdentity outgoingSubject) { var eidasLightRequest = validatedRequest.Message; @@ -125,7 +128,7 @@ private Task CreateResponseAsync(ValidatedEidasLightRequest { InResponseToId = eidasLightRequest.Id, IpAddress = _contextAccessor.HttpContext.GetClientIpAddress(), - Issuer = _contextAccessor.HttpContext.GetIdentityServerIssuerUri(), + Issuer = await _issuerNameService.GetCurrentAsync(), RelayState = eidasLightRequest.RelayState, Status = new EidasLightResponseStatus() { @@ -176,7 +179,7 @@ private Task CreateResponseAsync(ValidatedEidasLightRequest } } - return Task.FromResult(eidasLightResponse); + return eidasLightResponse; } } } \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/Services/DefaultIssuerNameService.cs b/src/Abc.IdentityServer.EidasLight/Services/DefaultIssuerNameService.cs new file mode 100644 index 0000000..a2ba278 --- /dev/null +++ b/src/Abc.IdentityServer.EidasLight/Services/DefaultIssuerNameService.cs @@ -0,0 +1,75 @@ +#if IDS4 + +using Abc.IdentityServer.Extensions; +using Microsoft.AspNetCore.Http; +using System; +using System.Threading.Tasks; + +namespace IdentityServer4.Services; + +/// +/// Abstracts issuer name access. +/// +public class DefaultIssuerNameService : IIssuerNameService +{ + private readonly IdentityServerOptions _options; + private readonly IServerUrls _urls; + private readonly IHttpContextAccessor _httpContextAccessor; + + /// + /// Initializes a new instance of the class. + /// + /// The identity server options. + /// The server uris. + /// The HTTP context accessor. + public DefaultIssuerNameService(IdentityServerOptions options, IServerUrls urls, IHttpContextAccessor httpContextAccessor) + { + _options = options; + _urls = urls; + _httpContextAccessor = httpContextAccessor; + } + + /// + public Task GetCurrentAsync() + { + // if they've explicitly configured a URI then use it, + // otherwise dynamically calculate it + var issuer = _options.IssuerUri; + if (issuer.IsMissing()) + { + string origin = null; + + if (_options.MutualTls.Enabled && _options.MutualTls.DomainName.IsPresent() + && !_options.MutualTls.DomainName.Contains(".")) + { + var request = _httpContextAccessor.HttpContext.Request; + if (request.Host.Value.StartsWith(_options.MutualTls.DomainName, StringComparison.OrdinalIgnoreCase)) + { + // if MTLS is configured with domain like "foo", then the request will be for "foo.acme.com", + // so the issuer we use is from the parent domain (e.g. "acme.com") + // + // Host.Value is used to get unicode hostname, instead of ToUriComponent (aka punycode) + origin = request.Scheme + "://" + request.Host.Value.Substring(_options.MutualTls.DomainName.Length + 1); + } + } + + if (origin == null) + { + // no MTLS, so use the current origin for the issuer + // this also means we emit the issuer value in unicode + origin = _urls.GetUnicodeOrigin(); + } + + issuer = origin + _urls.BasePath; + + if (_options.LowerCaseIssuerUri) + { + issuer = issuer.ToLowerInvariant(); + } + } + + return Task.FromResult(issuer); + } +} + +#endif \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/Services/DefaultServerUrls.cs b/src/Abc.IdentityServer.EidasLight/Services/DefaultServerUrls.cs new file mode 100644 index 0000000..ba58174 --- /dev/null +++ b/src/Abc.IdentityServer.EidasLight/Services/DefaultServerUrls.cs @@ -0,0 +1,60 @@ +#if IDS4 + +using Abc.IdentityServer.Extensions; +using Microsoft.AspNetCore.Http; +using System; +using System.Linq; + +namespace IdentityServer4.Services; + +/// +/// Implements . +/// +public class DefaultServerUrls : IServerUrls +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP context accessor. + public DefaultServerUrls(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + /// + public string Origin + { + get + { + var request = _httpContextAccessor.HttpContext.Request; + return request.Scheme + "://" + request.Host.ToUriComponent(); + } + + set + { + var split = value.Split(new[] { "://" }, StringSplitOptions.RemoveEmptyEntries); + + var request = _httpContextAccessor.HttpContext.Request; + request.Scheme = split.First(); + request.Host = new HostString(split.Last()); + } + } + + /// + public string BasePath + { + get + { + return _httpContextAccessor.HttpContext.Items["idsvr:IdentityServerBasePath"] as string; + } + + set + { + _httpContextAccessor.HttpContext.Items["idsvr:IdentityServerBasePath"] = value.RemoveTrailingSlash(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/Services/IIssuerNameService.cs b/src/Abc.IdentityServer.EidasLight/Services/IIssuerNameService.cs new file mode 100644 index 0000000..0437bab --- /dev/null +++ b/src/Abc.IdentityServer.EidasLight/Services/IIssuerNameService.cs @@ -0,0 +1,19 @@ +#if IDS4 + +using System.Threading.Tasks; + +namespace IdentityServer4.Services; + +/// +/// Abstract access to the current issuer name. +/// +public interface IIssuerNameService +{ + /// + /// Returns the issuer name for the current request. + /// + /// The issuer name. + Task GetCurrentAsync(); +} + +#endif \ No newline at end of file diff --git a/src/Abc.IdentityServer.EidasLight/Services/IServerUrls.cs b/src/Abc.IdentityServer.EidasLight/Services/IServerUrls.cs new file mode 100644 index 0000000..de9b53c --- /dev/null +++ b/src/Abc.IdentityServer.EidasLight/Services/IServerUrls.cs @@ -0,0 +1,26 @@ +#if IDS4 + +namespace IdentityServer4.Services; + +/// +/// Configures the per-request URLs and paths into the current server. +/// +public interface IServerUrls +{ + /// + /// Gets or sets the origin for IdentityServer. For example, "https://server.acme.com:5001". + /// + string Origin { get; set; } + + /// + /// Gets or sets the base path of IdentityServer. + /// + string BasePath { get; set; } + + /// + /// Gets the base URL for IdentityServer. + /// + string BaseUrl { get => Origin + BasePath; } +} + +#endif \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 24e9850..f58f28b 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -9,7 +9,7 @@ https://github.com/abc-software/Abc.IdentityServer.EidasLight.git git - 1.1.0-dev01 + 1.2.0-dev01 true ids.snk @@ -36,13 +36,13 @@ all runtime; build; native; contentfiles; analyzers - + all all - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/Abc.DuendeIdentityServer.EidasLight.UnitTests.csproj b/test/Abc.IdentityServer.EidasLight.UnitTests/Abc.DuendeIdentityServer.EidasLight.UnitTests.csproj index 42d573c..9fc41d0 100644 --- a/test/Abc.IdentityServer.EidasLight.UnitTests/Abc.DuendeIdentityServer.EidasLight.UnitTests.csproj +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/Abc.DuendeIdentityServer.EidasLight.UnitTests.csproj @@ -6,7 +6,7 @@ - net6.0 + net6.0;net8.0 10.0 $(DefineConstants);DUENDE diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/ErrorPageResultFixture.cs b/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/ErrorPageResultFixture.cs index aa8b2dd..6b963d7 100644 --- a/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/ErrorPageResultFixture.cs +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/ErrorPageResultFixture.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Authentication; +using Abc.IdentityServer.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using System; @@ -12,24 +12,29 @@ public class ErrorPageResultFixture { private ErrorPageResult _target; private IdentityServerOptions _options; + private MockServerUrls _urls; private MockMessageStore _errorMessageStore; - private ISystemClock _clock = new StubClock(); + private IClock _clock = new StubClock(); private DefaultHttpContext _context; public ErrorPageResultFixture() { _context = new DefaultHttpContext(); - _context.SetIdentityServerOrigin("https://server"); - _context.SetIdentityServerBasePath("/"); _context.Response.Body = new MemoryStream(); _options = new IdentityServerOptions(); _options.UserInteraction.ErrorUrl = "~/error"; _options.UserInteraction.ErrorIdParameter = "errorId"; + _urls = new MockServerUrls() + { + Origin = "https://server", + BasePath = "/".RemoveTrailingSlash(), // as in DefaultServerUrls + }; + _errorMessageStore = new MockMessageStore(); - _target = new ErrorPageResult("some_error", "some_desciption", _options, _clock, _errorMessageStore); + _target = new ErrorPageResult("some_error", "some_desciption", _options, _clock, _urls, _errorMessageStore); } [Fact] diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/LoginPageResultFixture.cs b/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/LoginPageResultFixture.cs index 73b85f0..47a1a91 100644 --- a/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/LoginPageResultFixture.cs +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/LoginPageResultFixture.cs @@ -1,6 +1,6 @@ using Abc.IdentityModel.Protocols.EidasLight; using Abc.IdentityServer.EidasLight.Validation; -using Microsoft.AspNetCore.Authentication; +using Abc.IdentityServer.Extensions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.WebUtilities; using System; @@ -14,8 +14,9 @@ public class LoginPageResultFixture { private LoginPageResult _target; private ValidatedEidasLightRequest _request; + private MockServerUrls _urls; private IdentityServerOptions _options; - private ISystemClock _clock = new StubClock(); + private IClock _clock = new StubClock(); private DefaultHttpContext _context; private EidasLightProtocolSerializer _protocolSerializer = new EidasLightProtocolSerializer(); private AuthorizationParametersMessageStoreMock _authorizationParametersMessageStore; @@ -23,8 +24,6 @@ public class LoginPageResultFixture public LoginPageResultFixture() { _context = new DefaultHttpContext(); - _context.SetIdentityServerOrigin("https://server"); - _context.SetIdentityServerBasePath("/"); _context.Response.Body = new MemoryStream(); _options = new IdentityServerOptions(); @@ -35,7 +34,13 @@ public LoginPageResultFixture() _request = new ValidatedEidasLightRequest(); - _target = new LoginPageResult(_request, _options, _clock, _protocolSerializer, _authorizationParametersMessageStore); + _urls = new MockServerUrls() + { + Origin = "https://server", + BasePath = "/".RemoveTrailingSlash(), // as in DefaultServerUrls + }; + + _target = new LoginPageResult(_request, _options, _clock, _urls, _protocolSerializer, _authorizationParametersMessageStore); } [Fact] diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/SignInResultFixture.cs b/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/SignInResultFixture.cs index 75e3bbc..719d915 100644 --- a/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/SignInResultFixture.cs +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/Endpoints/Results/SignInResultFixture.cs @@ -16,8 +16,6 @@ public SignInResultFixture() _options = new IdentityServerOptions(); _context = new DefaultHttpContext(); - _context.SetIdentityServerOrigin("https://server"); - _context.SetIdentityServerBasePath("/"); _context.Response.Body = new MemoryStream(); _target = new SignInResult("some_token", "http://client/callback", _options); diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/Extensions/StringExtensionsFixture.cs b/test/Abc.IdentityServer.EidasLight.UnitTests/Extensions/StringExtensionsFixture.cs index ba91eb5..27f7ebf 100644 --- a/test/Abc.IdentityServer.EidasLight.UnitTests/Extensions/StringExtensionsFixture.cs +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/Extensions/StringExtensionsFixture.cs @@ -167,6 +167,21 @@ public void CheckRemoveLeadingSlash(string inputUrl, string expected) actualOrigin.Should().Be(expected); } + + + [Theory] + [InlineData(null, null)] + [InlineData("", "")] + [InlineData("test/resource/", "test/resource")] + [InlineData("/test/resource/", "/test/resource")] + [InlineData("test/resource", "test/resource")] + [InlineData("/test/resource", "/test/resource")] + public void CheckRemoveTrailingSlash(string inputUrl, string expected) + { + var actualOrigin = inputUrl.RemoveTrailingSlash(); + + actualOrigin.Should().Be(expected); + } #endregion #region AddQueryString diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/GlobalUsings.cs b/test/Abc.IdentityServer.EidasLight.UnitTests/GlobalUsings.cs index d797591..e9fbeb2 100644 --- a/test/Abc.IdentityServer.EidasLight.UnitTests/GlobalUsings.cs +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/GlobalUsings.cs @@ -25,3 +25,9 @@ global using Ids = IdentityServer4; global using StatusCodeResult = IdentityServer4.Endpoints.Results.StatusCodeResult; #endif + +#if NET8_0_OR_GREATER +global using IClock = Duende.IdentityServer.IClock; +#else +global using IClock = Microsoft.AspNetCore.Authentication.ISystemClock; +#endif \ No newline at end of file diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/Internal/MockServerUrls.cs b/test/Abc.IdentityServer.EidasLight.UnitTests/Internal/MockServerUrls.cs new file mode 100644 index 0000000..3a891d2 --- /dev/null +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/Internal/MockServerUrls.cs @@ -0,0 +1,8 @@ +namespace Abc.IdentityServer +{ + public class MockServerUrls : IServerUrls + { + public string Origin { get; set; } + public string BasePath { get; set; } + } +} diff --git a/test/Abc.IdentityServer.EidasLight.UnitTests/Internal/StubClock.cs b/test/Abc.IdentityServer.EidasLight.UnitTests/Internal/StubClock.cs index 1ebf9e0..90241e3 100644 --- a/test/Abc.IdentityServer.EidasLight.UnitTests/Internal/StubClock.cs +++ b/test/Abc.IdentityServer.EidasLight.UnitTests/Internal/StubClock.cs @@ -1,8 +1,8 @@ using System; -namespace Microsoft.AspNetCore.Authentication +namespace Abc.IdentityServer { - internal class StubClock : ISystemClock + internal class StubClock : IClock { public Func UtcNowFunc = () => DateTime.UtcNow; public DateTimeOffset UtcNow => new DateTimeOffset(UtcNowFunc());