Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Cli.Tests/ConfigureOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ public void TestUpdateEnabledForRestSettings(bool updatedEnabledValue)
[DataRow("/updatedPath", DisplayName = "Update REST path to /updatedPath.")]
[DataRow("/updated_Path", DisplayName = "Ensure underscore is allowed in REST path.")]
[DataRow("/updated-Path", DisplayName = "Ensure hyphen is allowed in REST path.")]
[DataRow("/api/v2", DisplayName = "Ensure multi-segment paths are allowed in REST path.")]
public void TestUpdatePathForRestSettings(string updatedPathValue)
{
// Arrange -> all the setup which includes creating options.
Expand Down
6 changes: 4 additions & 2 deletions src/Cli.Tests/EndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,11 +343,12 @@ public void TestUpdateDepthLimitInGraphQLRuntimeSettings(string depthLimit, bool
[DataRow("/updatedPath", true, DisplayName = "Success in updated GraphQL Path to /updatedPath.")]
[DataRow("/updated-Path", true, DisplayName = "Success in updated GraphQL Path to /updated-Path.")]
[DataRow("/updated_Path", true, DisplayName = "Success in updated GraphQL Path to /updated_Path.")]
[DataRow("/api/v2", true, DisplayName = "Success in updated GraphQL Path to multi-segment path /api/v2.")]
[DataRow("updatedPath", false, DisplayName = "Failure due to '/' missing.")]
[DataRow("/updated Path", false, DisplayName = "Failure due to white spaces.")]
[DataRow("/updated.Path", false, DisplayName = "Failure due to reserved char '.'.")]
[DataRow("/updated@Path", false, DisplayName = "Failure due reserved chars '@'.")]
[DataRow("/updated/Path", false, DisplayName = "Failure due reserved chars '/'.")]
[DataRow("/api//v2", false, DisplayName = "Failure due to empty path segment.")]
public void TestUpdateGraphQLPathRuntimeSettings(string path, bool isSuccess)
{
// Initialize the config file.
Expand Down Expand Up @@ -405,11 +406,12 @@ public void TestUpdateHostCorsOriginsRuntimeSettings(string path, bool isSuccess
[DataRow("/updatedPath", true, DisplayName = "Successfully updated Rest Path to /updatedPath.")]
[DataRow("/updated-Path", true, DisplayName = "Successfully updated Rest Path to /updated-Path.")]
[DataRow("/updated_Path", true, DisplayName = "Successfully updated Rest Path to /updated_Path.")]
[DataRow("/api/v2", true, DisplayName = "Successfully updated Rest Path to multi-segment path /api/v2.")]
[DataRow("updatedPath", false, DisplayName = "Failure due to '/' missing.")]
[DataRow("/updated Path", false, DisplayName = "Failure due to white spaces.")]
[DataRow("/updated.Path", false, DisplayName = "Failure due to reserved char '.'.")]
[DataRow("/updated@Path", false, DisplayName = "Failure due reserved chars '@'.")]
[DataRow("/updated/Path", false, DisplayName = "Failure due reserved chars '/'.")]
[DataRow("/api//v2", false, DisplayName = "Failure due to empty path segment.")]
public void TestUpdateRestPathRuntimeSettings(string path, bool isSuccess)
{
// Initialize the config file.
Expand Down
3 changes: 2 additions & 1 deletion src/Cli/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ public static bool IsURIComponentValid(string? uriComponent)
uriComponent = uriComponent.Substring(1);
}

return !RuntimeConfigValidatorUtil.DoesUriComponentContainReservedChars(uriComponent);
// The path may contain multiple '/'-separated segments (e.g. 'api/v2'); validate each segment.
return !RuntimeConfigValidatorUtil.DoesUriPathContainReservedChars(uriComponent);
}

/// <summary>
Expand Down
33 changes: 31 additions & 2 deletions src/Core/Configurations/RuntimeConfigValidatorUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ public static bool TryValidateUriComponent(string? uriComponent, out string exce
}
else
{
// Remove the leading '/' before validating the remaining path. The path may contain
// multiple '/'-separated segments (e.g. '/api/v2'), each of which is validated individually.
uriComponent = uriComponent.Substring(1);
// URI component should not contain any reserved characters.
if (DoesUriComponentContainReservedChars(uriComponent))
if (DoesUriPathContainReservedChars(uriComponent))
{
exceptionMessageSuffix = URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG;
}
Expand All @@ -66,6 +67,34 @@ public static bool DoesUriComponentContainReservedChars(string uriComponent)
return _reservedUriCharsRgx.IsMatch(uriComponent);
}

/// <summary>
/// Method to validate a URI path that may contain multiple '/'-separated segments
/// (for example 'api/v2'). The leading '/' is expected to already be removed.
/// Each segment is validated to ensure it is non-empty and free of reserved characters.
/// An empty input (representing the root path '/') is considered valid.
/// </summary>
/// <param name="uriPath">Path prefix for rest/graphql apis with the leading '/' already removed.</param>
/// <returns>true if any segment is empty or contains reserved characters, false otherwise.</returns>
Comment on lines +73 to +77
public static bool DoesUriPathContainReservedChars(string uriPath)
{
// An empty path represents the root '/' which is valid and contains no segments to validate.
if (string.IsNullOrEmpty(uriPath))
{
return false;
}

foreach (string segment in uriPath.Split('/'))
{
// An empty segment indicates leading, consecutive, or trailing slashes.
if (string.IsNullOrEmpty(segment) || DoesUriComponentContainReservedChars(segment))
{
return true;
}
}

return false;
}

/// <summary>
/// Method to validate an entity REST path allowing sub-directories (forward slashes).
/// Each segment of the path is validated for reserved characters and path traversal patterns.
Expand Down
8 changes: 8 additions & 0 deletions src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,14 @@ private static void ValidateExceptionForDuplicateQueriesDueToEntityDefinitions(S
DisplayName = "GraphQL path prefix containing space at the start and underscore in between.")]
[DataRow("/", null, ApiType.REST, false,
DisplayName = "REST path containing only a forward slash.")]
[DataRow("/api/v2", null, ApiType.REST, false,
DisplayName = "REST path containing multiple segments.")]
[DataRow("/api/v2", null, ApiType.GraphQL, false,
DisplayName = "GraphQL path containing multiple segments.")]
[DataRow("/api/", $"REST path {RuntimeConfigValidatorUtil.URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG}", ApiType.REST, true,
DisplayName = "REST path containing a trailing slash.")]
[DataRow("/api//v2", $"REST path {RuntimeConfigValidatorUtil.URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG}", ApiType.REST, true,
DisplayName = "REST path containing an empty segment.")]
public void ValidateApiURIsAreWellFormed(
string apiPathPrefix,
string expectedErrorMessage,
Expand Down
Loading