Skip to content
Merged

Dev #2753

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
446ffd2
feature/AB#33848-AddTabPermisionToRole
JamesPasta Jul 30, 2026
1fb6da4
AB#33409: Fix SQL Injection on Reporting Configuration
aurelio-aot Jul 30, 2026
666bc51
AB#33409: More Unit Tests For SQL Injection on Reporting
aurelio-aot Jul 30, 2026
285c83f
AB#33922 aria labels for merge applicant
AndreGAot Jul 31, 2026
0e75258
AB#33922 remaining missing aria-labels
AndreGAot Jul 31, 2026
7e8c756
AB#33922 address sonarQ feedback
AndreGAot Jul 31, 2026
415e3f1
AB#33922 add missing label
AndreGAot Jul 31, 2026
c52351c
Merge pull request #2746 from bcgov/feature/AB#33922-aria-labels
AndreGAot Jul 31, 2026
d678c53
AB#33922 update sonarqube keyboard interactions
AndreGAot Jul 31, 2026
9c5c6e4
AB#33922 codeQL feedback
AndreGAot Jul 31, 2026
7b21a45
Merge pull request #2747 from bcgov/feature/AB#33922-keyboard-interac…
AndreGAot Jul 31, 2026
8ab70fc
feature/AB#33938-TinyMceTools-missedtheedit
JamesPasta Jul 31, 2026
e985b52
AB#33922 fix the sonar properties exlusion
AndreGAot Jul 31, 2026
61429fe
Merge pull request #2748 from bcgov/feature/AB#33922-keyboard-interac…
AndreGAot Jul 31, 2026
d6dd8fc
AB#33922 form labels
AndreGAot Jul 31, 2026
62158de
feature/AB#33938-TinyMceTools-missedtheMenuCss
JamesPasta Jul 31, 2026
21e904c
Merge pull request #2749 from bcgov/bugfix/AB#33938-TinyMCEToolbar
JamesPasta Jul 31, 2026
4cf0d3b
AB#33409: Remove filter and orderBy Parameters Instead
aurelio-aot Jul 31, 2026
24d3a2c
Merge pull request #2750 from bcgov/feature/AB#33922-form-labels
JamesPasta Jul 31, 2026
a27b11f
Merge pull request #2741 from bcgov/bugfix/AB#33409-SQL-Injection-On-…
JamesPasta Jul 31, 2026
ede275d
AB#33922 aria checks for switches
AndreGAot Jul 31, 2026
a2d81d8
Potential fix for pull request finding
AndreGAot Jul 31, 2026
1dc5b2a
AB#33922 address pr feedback
AndreGAot Jul 31, 2026
ed6fcf2
Merge branch 'feature/AB#33922-aria-swtich-labels' of https://github.…
AndreGAot Jul 31, 2026
4616dd1
AB#33922 codeQL fixes
AndreGAot Jul 31, 2026
509cbd1
Merge pull request #2751 from bcgov/feature/AB#33922-aria-swtich-labels
AndreGAot Jul 31, 2026
04c6fb0
Merge pull request #2752 from bcgov/feature/AB#33848-FixAddingTopLeve…
JamesPasta Jul 31, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
@if (!isEditable)
{
<label class="form-label">@df.Name</label>
<input type="text" class="form-control" value="@df.Value" disabled />
<input type="text" class="form-control" value="@df.Value" aria-label="@df.Name" disabled />
}
else if (fieldType == CustomFieldType.Checkbox)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
foreach (var option in Model.Options)
{
<div class="option-container" id="option@(Model.Options.IndexOf(option) + 1)Container">
<input type="radio" name="radio_input" />
<input type="radio" name="radio_input" aria-label="@option" />
<input type="text" id="option@(Model.Options.IndexOf(option) + 1)" name="Options" class="form-control" value="@option" minlength="1" maxlength="60" />
<button type="button" class="btn btn-danger btn-sm remove-option-btn" data-option-id="option@(Model.Options.IndexOf(option) + 1)Container">Delete Option</button>
</div>
Expand Down Expand Up @@ -56,7 +56,7 @@
newOption.className = "option-container";
newOption.id = optionId;
newOption.innerHTML = `
<input type="radio" name="radio_input" />
<input type="radio" name="radio_input" aria-label="Radio option" />
<input type="text" id="option${optionCount}" name="Options" class="form-control" minlength="1" maxlength="60" />
<button type="button" class="btn btn-danger btn-sm remove-option-btn" data-option-id="${optionId}">Delete Option</button>`;
document.getElementById("radioOptions").appendChild(newOption);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<abp-row>
<abp-column size="_12" class="p-3">
<span class="form-label">Account Coding</span>
<input id="account-coding" type="text" value="" readonly="" class="valid form-control readonly" aria-invalid="false">
<input id="account-coding" type="text" value="" readonly="" class="valid form-control readonly" aria-invalid="false" aria-label="Account Coding">
</abp-column>
</abp-row>
</abp-modal-body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<abp-row>
<abp-column size="_12" class="p-3">
<span class="form-label">Account Coding</span>
<input id="account-coding" type="text" value="" readonly="" class="valid form-control readonly" aria-invalid="false">
<input id="account-coding" type="text" value="" readonly="" class="valid form-control readonly" aria-invalid="false" aria-label="Account Coding">
</abp-column>
</abp-row>
<abp-input hidden="true" asp-for="Id" suppress-label="true"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<h4>Account Codes</h4>
</div>
<div class="filter-search-action-bar_search-wrapper">
<input type="search" id="search-data-table" placeholder="Search" class="tbl-search">
<input type="search" id="search-data-table" placeholder="Search" aria-label="Search" class="tbl-search">
</div>

<div class="btn-group" id="app_custom_buttons"></div>
Expand All @@ -61,7 +61,7 @@
<abp-column size="_12" class="p-3">
<span class="form-label">Payment ID Prefix</span>
<input id="payment-id-prefix-original" type="hidden" value="@Model.PaymentIdPrefix">
<input id="payment-id-prefix" maxlength="5" type="text" value="@Model.PaymentIdPrefix" class="valid form-control">
<input id="payment-id-prefix" maxlength="5" type="text" value="@Model.PaymentIdPrefix" class="valid form-control" aria-label="Payment ID Prefix">
</abp-column>
</abp-row>
<div class="d-flex p-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<div class="action-bar p-2 search-action-bar">
<div class="search-action-bar_search-wrapper">
<input type="search" id="search" placeholder="Search" class="tbl-search">
<input type="search" id="search" placeholder="Search" aria-label="Search" class="tbl-search">

@if (Model)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,10 @@
bool viewExists = await columnsMappingService.ViewExistsAsync("my_form_view");

// Get view data with pagination
var request = new ViewDataRequest
var request = new ViewDataRequest
{
Skip = 0,
Take = 100,
Filter = "column_name IS NOT NULL", // Optional SQL WHERE clause
OrderBy = "column_name ASC" // Optional SQL ORDER BY clause
Take = 100
};

ViewDataResult data = await columnsMappingService.GetViewDataAsync("my_form_view", request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,18 @@ public interface IReportMappingService
public Task<ViewGenerationResult> GenerateViewAsync(Guid correlationId, string correlationProvider, string viewName);

/// <summary>
/// Retrieves paginated and filtered data from a generated database view with support for sorting and custom filtering.
/// Retrieves paginated data from a generated database view.
/// </summary>
/// <param name="viewName">The name of the database view to query for data.</param>
/// <param name="request">The request parameters containing pagination settings, filtering criteria, and sort ordering.</param>
/// <param name="request">The request parameters containing pagination settings.</param>
/// <returns>A ViewDataResult containing the queried data rows, total record count, and column information for the requested page.</returns>
public Task<ViewDataResult> GetViewDataAsync(string viewName, ViewDataRequest request);

/// <summary>
/// Retrieves preview data from a generated database view showing only the top record for preview purposes.
/// </summary>
/// <param name="viewName">The name of the database view to query for preview data.</param>
/// <param name="request">The request parameters for filtering (pagination settings are ignored as only top 1 record is returned).</param>
/// <param name="request">The request parameters (pagination settings are ignored as only top 1 record is returned).</param>
/// <returns>A ViewDataResult containing the preview data (single top record), count of 1, and column information.</returns>
public Task<ViewDataResult> GetViewPreviewDataAsync(string viewName, ViewDataRequest request);

Expand All @@ -132,8 +132,8 @@ public interface IReportMappingService
}

/// <summary>
/// Represents a request for view data with pagination, filtering, and sorting options.
/// Provides flexible data retrieval parameters for querying generated reporting views.
/// Represents a request for view data with pagination options.
/// Provides data retrieval parameters for querying generated reporting views.
/// </summary>
public class ViewDataRequest
{
Expand All @@ -148,18 +148,6 @@ public class ViewDataRequest
/// Defaults to 100 to prevent excessive data transfer while allowing reasonable page sizes.
/// </summary>
public int Take { get; set; } = 100;

/// <summary>
/// Gets or sets the SQL WHERE clause filter to apply to the view query.
/// Should be a valid PostgreSQL WHERE clause condition without the "WHERE" keyword.
/// </summary>
public string? Filter { get; set; }

/// <summary>
/// Gets or sets the SQL ORDER BY clause to apply for result sorting.
/// Should be a valid PostgreSQL ORDER BY clause without the "ORDER BY" keywords.
/// </summary>
public string? OrderBy { get; set; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,12 +375,12 @@ await backgroundJobManager.EnqueueAsync(new GenerateViewBackgroundJobArgs
}

/// <summary>
/// Retrieves paginated and filtered data from a generated database view with support for sorting and custom filtering.
/// Retrieves paginated data from a generated database view.
/// Validates view existence, normalizes the view name, and delegates to the repository for secure data access
/// with proper pagination controls to handle large datasets efficiently.
/// </summary>
/// <param name="viewName">The name of the database view to query for data.</param>
/// <param name="request">The request parameters containing pagination settings (skip/take), filtering criteria, and sort ordering.</param>
/// <param name="request">The request parameters containing pagination settings (skip/take).</param>
/// <returns>A ViewDataResult containing the queried data rows, total record count, and column information for the requested page.</returns>
/// <exception cref="ArgumentException">
/// Thrown when:
Expand Down Expand Up @@ -411,7 +411,7 @@ public async Task<ViewDataResult> GetViewDataAsync(string viewName, ViewDataRequ
/// Validates view existence and normalizes the view name before querying.
/// </summary>
/// <param name="viewName">The name of the database view to query for preview data.</param>
/// <param name="request">The request parameters for filtering (pagination settings are ignored as only top 1 record is returned).</param>
/// <param name="request">The request parameters (pagination settings are ignored as only top 1 record is returned).</param>
/// <returns>A ViewDataResult containing the preview data (single top record), count of 1, and column information.</returns>
/// <exception cref="ArgumentException">
/// Thrown when:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public interface IReportColumnsMapRepository : IBasicRepository<ReportColumnsMap
Task GenerateViewAsync(Guid correlationId, string correlationProvider);

/// <summary>
/// Retrieves data from a generated view with pagination and filtering.
/// Retrieves data from a generated view with pagination.
/// </summary>
/// <param name="viewName">The name of the view to query.</param>
/// <param name="request">The request parameters for data retrieval.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ public async Task<ViewDataResult> GetViewPreviewDataAsync(string viewName, ViewD
// Normalize view name to lowercase for consistency
var normalizedViewName = viewName.Trim().ToLowerInvariant();

// SECURITY: Validate the identifier before it is interpolated into SQL below.
// ViewExistsAsync alone is not sufficient - it only proves a matching row exists in
// pg_views, not that the name is free of characters that would break out of the
// quoted identifier it gets embedded in.
if (!IsValidPostgreSqlIdentifier(normalizedViewName))
{
throw new ArgumentException($"Invalid view name format: {viewName}", nameof(viewName));
}

var dbContext = await GetDbContextAsync();
var connection = dbContext.Database.GetDbConnection();
await dbContext.Database.OpenConnectionAsync();
Expand All @@ -132,18 +141,6 @@ ORDER BY a.""CreationTime"" DESC
LIMIT 1
)";

// Add filtering if provided
if (!string.IsNullOrWhiteSpace(request.Filter))
{
previewQuery += $" AND ({request.Filter})";
}

// Add ordering if provided
if (!string.IsNullOrWhiteSpace(request.OrderBy))
{
previewQuery += $" ORDER BY {request.OrderBy}";
}

// Execute the preview query
using var dataCommand = connection.CreateCommand();
dataCommand.CommandText = previewQuery;
Expand Down Expand Up @@ -180,6 +177,15 @@ public async Task<ViewDataResult> GetViewDataAsync(string viewName, ViewDataRequ
// Normalize view name to lowercase for consistency
var normalizedViewName = viewName.Trim().ToLowerInvariant();

// SECURITY: Validate the identifier before it is interpolated into SQL below.
// ViewExistsAsync alone is not sufficient - it only proves a matching row exists in
// pg_views, not that the name is free of characters that would break out of the
// quoted identifier it gets embedded in.
if (!IsValidPostgreSqlIdentifier(normalizedViewName))
{
throw new ArgumentException($"Invalid view name format: {viewName}", nameof(viewName));
}

var dbContext = await GetDbContextAsync();
var connection = dbContext.Database.GetDbConnection();
await dbContext.Database.OpenConnectionAsync();
Expand All @@ -196,14 +202,6 @@ public async Task<ViewDataResult> GetViewDataAsync(string viewName, ViewDataRequ
var baseQuery = $@"SELECT * FROM ""Reporting"".""{normalizedViewName}""";
var countQuery = $@"SELECT COUNT(*) FROM ""Reporting"".""{normalizedViewName}""";

// Add filtering if provided
if (!string.IsNullOrWhiteSpace(request.Filter))
{
var whereClause = $" WHERE {request.Filter}";
baseQuery += whereClause;
countQuery += whereClause;
}

// Get total count
using (var countCommand = connection.CreateCommand())
{
Expand All @@ -212,12 +210,6 @@ public async Task<ViewDataResult> GetViewDataAsync(string viewName, ViewDataRequ
result.TotalCount = Convert.ToInt32(countResult);
}

// Add ordering if provided
if (!string.IsNullOrWhiteSpace(request.OrderBy))
{
baseQuery += $" ORDER BY {request.OrderBy}";
}

// Add pagination
baseQuery += $" OFFSET {request.Skip} LIMIT {request.Take}";

Expand Down Expand Up @@ -399,6 +391,14 @@ FROM pg_views
// Grant SELECT permission on each view to the role
foreach (var viewName in viewNames)
{
// SECURITY: Validate each identifier read back from pg_views before it is
// interpolated into SQL - quoted PostgreSQL identifiers can contain characters
// (embedded quotes, semicolons) that would otherwise break out of the quotes below.
if (!IsValidPostgreSqlIdentifier(viewName))
{
throw new ArgumentException($"Invalid view name format: {viewName}", nameof(viewName));
}

var sql = $"GRANT SELECT ON \"Reporting\".\"{viewName}\" TO \"{role}\"";
await dbContext.Database.ExecuteSqlRawAsync(sql);
}
Expand Down Expand Up @@ -551,7 +551,7 @@ FROM information_schema.views
/// </summary>
/// <param name="identifier">The identifier to validate</param>
/// <returns>True if the identifier is valid, false otherwise</returns>
private static bool IsValidPostgreSqlIdentifier(string identifier)
internal static bool IsValidPostgreSqlIdentifier(string identifier)
{
if (string.IsNullOrWhiteSpace(identifier))
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
value="@tenantRole.ViewRole"
data-tenant-id="@tenantRole.TenantId"
data-is-default="@tenantRole.IsDefaultInferred"
placeholder="@($"{tenantRole.TenantName.ToLowerInvariant()}_readonly")" />
placeholder="@($"{tenantRole.TenantName.ToLowerInvariant()}_readonly")"
aria-label="@($"View role for {tenantRole.TenantName}")" />
@if (tenantRole.IsDefaultInferred)
{
<i class="fa fa-exclamation-triangle text-warning ms-2 default-role-indicator"
Expand Down
Loading
Loading