Skip to content

Commit

Permalink
Merge pull request #209 from DuendeSoftware/brock/return_url_parser_s…
Browse files Browse the repository at this point in the history
…upport_full_urls

Allow full host name to be included in OidcReturnUrlParser's IsValidReturnUrl
  • Loading branch information
brockallen authored Apr 23, 2021
2 parents be81b55 + cd57529 commit 6a86b06
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,10 @@ public class UserInteractionOptions
/// The device verification user code parameter.
/// </value>
public string DeviceVerificationUserCodeParameter { get; set; } = Constants.UIConstants.DefaultRoutePathParams.UserCode;

/// <summary>
/// Flag that allows return URL validation to accept full URL that includes the IdentityServer origin. Defaults to false.
/// </summary>
public bool AllowOriginInReturnUrl { get; set; }
}
}
40 changes: 36 additions & 4 deletions src/IdentityServer/Services/Default/OidcReturnUrlParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Duende Software. All rights reserved.
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.


Expand All @@ -10,24 +10,32 @@
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Stores;
using Duende.IdentityServer.Validation;
using Microsoft.AspNetCore.Http;
using Duende.IdentityServer.Configuration;

namespace Duende.IdentityServer.Services
{
internal class OidcReturnUrlParser : IReturnUrlParser
{
private readonly IdentityServerOptions _options;
private readonly IAuthorizeRequestValidator _validator;
private readonly IUserSession _userSession;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger _logger;
private readonly IAuthorizationParametersMessageStore _authorizationParametersMessageStore;

public OidcReturnUrlParser(
IdentityServerOptions options,
IAuthorizeRequestValidator validator,
IUserSession userSession,
IHttpContextAccessor httpContextAccessor,
ILogger<OidcReturnUrlParser> logger,
IAuthorizationParametersMessageStore authorizationParametersMessageStore = null)
{
_options = options;
_validator = validator;
_userSession = userSession;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_authorizationParametersMessageStore = authorizationParametersMessageStore;
}
Expand Down Expand Up @@ -59,12 +67,36 @@ public async Task<AuthorizationRequest> ParseAsync(string returnUrl)

public bool IsValidReturnUrl(string returnUrl)
{
if (_options.UserInteraction.AllowOriginInReturnUrl && returnUrl != null)
{
if (!Uri.TryCreate(returnUrl, UriKind.RelativeOrAbsolute, out _))
{
_logger.LogTrace("returnUrl is not valid");
return false;
}

var host = _httpContextAccessor.HttpContext.GetIdentityServerHost();
if (returnUrl.StartsWith(host, StringComparison.OrdinalIgnoreCase) == true)
{
returnUrl = returnUrl.Substring(host.Length);
}
}

if (returnUrl.IsLocalUrl())
{
var index = returnUrl.IndexOf('?');
if (index >= 0)
{
returnUrl = returnUrl.Substring(0, index);
var index = returnUrl.IndexOf('?');
if (index >= 0)
{
returnUrl = returnUrl.Substring(0, index);
}
}
{
var index = returnUrl.IndexOf('#');
if (index >= 0)
{
returnUrl = returnUrl.Substring(0, index);
}
}

if (returnUrl.EndsWith(Constants.ProtocolRoutePaths.Authorize, StringComparison.Ordinal) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.

using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Services;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using Xunit;

namespace UnitTests.Services.Default
{
public class OidcReturnUrlParserTests
{
private OidcReturnUrlParser _subject;

IdentityServerOptions _options = new IdentityServerOptions();
DefaultHttpContext _httpContext = new DefaultHttpContext();

public OidcReturnUrlParserTests()
{
_httpContext.Request.Scheme = "https";
_httpContext.Request.Host = new HostString("server");

_subject = new OidcReturnUrlParser(
_options,
null, null,
new HttpContextAccessor { HttpContext = _httpContext },
new LoggerFactory().CreateLogger<OidcReturnUrlParser>());
}

[Theory]
[InlineData("/connect/authorize")]
[InlineData("/connect/authorize?foo=f1&bar=b1")]
[InlineData("/connect/authorize/callback")]
[InlineData("/connect/authorize/callback?foo=f1&bar=b1")]
[InlineData("/foo/connect/authorize")]
[InlineData("/foo/connect/authorize/callback")]
public void IsValidReturnUrl_accepts_authorize_or_authorizecallback(string url)
{
var valid = _subject.IsValidReturnUrl(url);
valid.Should().BeTrue();
}

[Theory]
[InlineData(default(string))]
[InlineData("")]
[InlineData("/")]
[InlineData("/path")]
[InlineData("//connect/authorize")]
[InlineData("/connect/authorizex")]
[InlineData("/connect")]
[InlineData("/connect/token")]
[InlineData("/authorize")]
[InlineData("/foo?/connect/authorize")]
[InlineData("/foo#/connect/authorize")]
[InlineData("/foo?#/connect/authorize")]
[InlineData("/foo#?/connect/authorize")]
[InlineData("//server/connect/authorize")]
public void IsValidReturnUrl_rejects_non_authorize_or_authorizecallback(string url)
{
var valid = _subject.IsValidReturnUrl(url);
valid.Should().BeFalse();
}

[Theory]
[InlineData("https://server/connect/authorize")]
[InlineData("HTTPS://server/connect/authorize")]
[InlineData("https://SERVER/connect/authorize")]
[InlineData("https://server/foo/connect/authorize")]
public void IsValidReturnUrl_accepts_urls_with_current_host(string url)
{
_options.UserInteraction.AllowOriginInReturnUrl = true;
var valid = _subject.IsValidReturnUrl(url);
valid.Should().BeTrue();
}

[Fact]
public void IsValidReturnUrl_when_AllowHostInReturnUrl_disabled_rejects_urls_with_current_host()
{
_options.UserInteraction.AllowOriginInReturnUrl = false;
var valid = _subject.IsValidReturnUrl("https://server/connect/authorize");
valid.Should().BeFalse();
}

[Theory]
[InlineData("http://server/connect/authorize")]
[InlineData("https:\\/server/connect/authorize")]
[InlineData("https:\\\\server/connect/authorize")]
[InlineData("https://foo/connect/authorize")]
[InlineData("https://serverhttps://server/connect/authorize")]
[InlineData("https://serverfoo/connect/authorize")]
[InlineData("https://server//foo/connect/authorize")]
[InlineData("https://server:443/connect/authorize")]
public void IsValidReturnUrl_rejects_urls_with_incorrect_current_host(string url)
{
_options.UserInteraction.AllowOriginInReturnUrl = true;
var valid = _subject.IsValidReturnUrl(url);
valid.Should().BeFalse();
}


[Fact]
public void IsValidReturnUrl_accepts_urls_with_unicode()
{
_options.UserInteraction.AllowOriginInReturnUrl = true;
_httpContext.Request.Host = new HostString("грант.рф");

var valid = _subject.IsValidReturnUrl("https://xn--80af5akm.xn--p1ai/connect/authorize");
valid.Should().BeTrue();
}

[Theory]
[InlineData("https://server:443/connect/authorize")]
[InlineData("HTTPS://server:443/connect/authorize")]
[InlineData("https://SERVER:443/connect/authorize")]
public void IsValidReturnUrl_accepts_urls_with_current_port(string url)
{
_options.UserInteraction.AllowOriginInReturnUrl = true;
_httpContext.Request.Host = new HostString("server:443");

var valid = _subject.IsValidReturnUrl(url);
valid.Should().BeTrue();
}

[Theory]
[InlineData("https://server/connect/authorize")]
[InlineData("https://server:80/connect/authorize")]
[InlineData("https://server:4/connect/authorize")]
[InlineData("https://foo:443/connect/authorize")]
[InlineData("https://server:4433/connect/authorize")]
[InlineData("https://server:443https://server:443/connect/authorize")]
[InlineData("https://serverfoo:443/connect/authorize")]
[InlineData("https://server:443foo/connect/authorize")]
[InlineData("https://server:443//foo/connect/authorize")]
public void IsValidReturnUrl_rejects_urls_with_incorrect_current_port(string url)
{
_options.UserInteraction.AllowOriginInReturnUrl = true;
_httpContext.Request.Host = new HostString("server:443");

var valid = _subject.IsValidReturnUrl(url);
valid.Should().BeFalse();
}
}
}

0 comments on commit 6a86b06

Please sign in to comment.