From 60b4986a7ab313c084e570a143edd82634abd40d Mon Sep 17 00:00:00 2001
From: LIHE
Date: Thu, 22 Jan 2026 10:23:45 -0800
Subject: [PATCH 001/124] Add AI Permission Group
---
...ApplicationPermissionDefinitionProvider.cs | 22 +++++++++++++++++--
.../Localization/GrantManager/en.json | 4 ++++
.../GrantApplicationPermissions.cs | 19 ++++++++++++++--
.../Menus/GrantManagerMenuContributor.cs | 2 +-
.../Pages/GrantApplications/Details.cshtml | 4 +++-
.../ChefsAttachments/ChefsAttachments.cs | 10 +++++++--
6 files changed, 53 insertions(+), 8 deletions(-)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Permissions/GrantApplications/GrantApplicationPermissionDefinitionProvider.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Permissions/GrantApplications/GrantApplicationPermissionDefinitionProvider.cs
index 3eefe187f..428f8a5d6 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Permissions/GrantApplications/GrantApplicationPermissionDefinitionProvider.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application.Contracts/Permissions/GrantApplications/GrantApplicationPermissionDefinitionProvider.cs
@@ -1,6 +1,7 @@
using Unity.GrantManager.Localization;
using Unity.Modules.Shared;
using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.SettingManagement;
@@ -110,8 +111,25 @@ public override void Define(IPermissionDefinitionContext context)
tagsPermissionsGroup.AddPermission(UnitySelector.Application.Tags.Create, L(UnitySelector.Application.Tags.Create));
tagsPermissionsGroup.AddPermission(UnitySelector.Application.Tags.Delete, L(UnitySelector.Application.Tags.Delete));
- // AI
- grantApplicationPermissionsGroup.AddPermission(GrantApplicationPermissions.AIReporting.Default, L("Permission:GrantApplicationManagement.AIReporting.Default"));
+ // AI Permission Group
+ var aiPermissionsGroup = context.AddGroup(
+ GrantApplicationPermissions.AI.GroupName,
+ L("Permission:AI"));
+
+ aiPermissionsGroup.AddPermission(
+ GrantApplicationPermissions.AI.Reporting.Default,
+ L("Permission:AI.Reporting"))
+ .RequireFeatures("Unity.AIReporting");
+
+ aiPermissionsGroup.AddPermission(
+ GrantApplicationPermissions.AI.ApplicationAnalysis.Default,
+ L("Permission:AI.ApplicationAnalysis"))
+ .RequireFeatures("Unity.AI.ApplicationAnalysis");
+
+ aiPermissionsGroup.AddPermission(
+ GrantApplicationPermissions.AI.AttachmentSummary.Default,
+ L("Permission:AI.AttachmentSummary"))
+ .RequireFeatures("Unity.AI.AttachmentSummaries");
}
private static LocalizableString L(string name)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
index 26d4ceacc..fa18b68a3 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
@@ -150,6 +150,10 @@
"Permission:GrantManagerManagement.ApplicationForms.Default": "Manage Forms",
"Permission:GrantApplicationManagement.Approvals.BulkApplicationApproval": "Bulk Application Approval",
"Permission:GrantApplicationManagement.AIReporting.Default": "AI Reporting",
+ "Permission:AI": "AI",
+ "Permission:AI.Reporting": "AI Reporting",
+ "Permission:AI.ApplicationAnalysis": "AI Application Analysis",
+ "Permission:AI.AttachmentSummary": "AI Attachment Summary",
"ApplicationForms": "Forms",
"ApplicationForms:Description": "Description",
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantApplicationPermissions.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantApplicationPermissions.cs
index 15ff100ba..b3997090f 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantApplicationPermissions.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Permissions/GrantApplicationPermissions.cs
@@ -40,9 +40,24 @@ public static class Applicants
public const string AssignApplicant = Default + ".AssignApplicant";
}
- public static class AIReporting
+ public static class AI
{
- public const string Default = GroupName + ".AIReporting";
+ public const string GroupName = "AI";
+
+ public static class Reporting
+ {
+ public const string Default = GroupName + ".Reporting";
+ }
+
+ public static class ApplicationAnalysis
+ {
+ public const string Default = GroupName + ".ApplicationAnalysis";
+ }
+
+ public static class AttachmentSummary
+ {
+ public const string Default = GroupName + ".AttachmentSummary";
+ }
}
public static class Assignments
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs
index 0fb9506a3..f881f05e3 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenuContributor.cs
@@ -124,7 +124,7 @@ private async static Task ConfigureMainMenuAsync(MenuConfigurationContext contex
l["Menu:AIReporting"],
"~/AIReporting",
icon: "fl fl-view-dashboard",
- requiredPermissionName: GrantApplicationPermissions.AIReporting.Default,
+ requiredPermissionName: GrantApplicationPermissions.AI.Reporting.Default,
order: 9
)
);
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml
index 7cdfb1c55..f4894efe2 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml
@@ -4,6 +4,7 @@
@using Unity.Flex.Web.Views.Shared.Components.WorksheetInstanceWidget
@using Unity.GrantManager.Flex
@using Unity.GrantManager.Localization
+@using Unity.GrantManager.Permissions
@using Unity.GrantManager.Web.Pages.GrantApplications
@using Unity.GrantManager.Web.Views.Shared.Components.CustomTabWidget
@using Unity.GrantManager.Web.Views.Shared.Components.DetailsActionBar
@@ -29,7 +30,8 @@
PageLayout.Content.Title = L["Grants"].Value;
var notificationsFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Notifications");
var readEmailGranted = await PermissionChecker.IsGrantedAsync("Notifications.Email");
- var aiApplicationAnalysisEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis");
+ var aiApplicationAnalysisEnabled = await FeatureChecker.IsEnabledAsync("Unity.AI.ApplicationAnalysis")
+ && await PermissionChecker.IsGrantedAsync(GrantApplicationPermissions.AI.ApplicationAnalysis.Default);
var flexFeatureEnabled = await FeatureChecker.IsEnabledAsync("Unity.Flex");
}
@section styles
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs
index 48458c09f..57d32cff7 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ChefsAttachments/ChefsAttachments.cs
@@ -5,6 +5,8 @@
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using System.Collections.Generic;
using Volo.Abp.Features;
+using Volo.Abp.Authorization.Permissions;
+using Unity.GrantManager.Permissions;
namespace Unity.GrantManager.Web.Views.Shared.Components.ChefsAttachments
{
@@ -15,15 +17,19 @@ namespace Unity.GrantManager.Web.Views.Shared.Components.ChefsAttachments
public class ChefsAttachments : AbpViewComponent
{
private readonly IFeatureChecker _featureChecker;
+ private readonly IPermissionChecker _permissionChecker;
- public ChefsAttachments(IFeatureChecker featureChecker)
+ public ChefsAttachments(IFeatureChecker featureChecker, IPermissionChecker permissionChecker)
{
_featureChecker = featureChecker;
+ _permissionChecker = permissionChecker;
}
public async Task InvokeAsync()
{
- var isAIAttachmentSummariesEnabled = await _featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries");
+ var isAIAttachmentSummariesEnabled =
+ await _featureChecker.IsEnabledAsync("Unity.AI.AttachmentSummaries") &&
+ await _permissionChecker.IsGrantedAsync(GrantApplicationPermissions.AI.AttachmentSummary.Default);
ViewBag.IsAIAttachmentSummariesEnabled = isAIAttachmentSummariesEnabled;
return View();
}
From dcf11f5bcec57044b654d90c749f7f8ee3aeadf9 Mon Sep 17 00:00:00 2001
From: Stephan McColm
Date: Thu, 22 Jan 2026 18:59:11 -0800
Subject: [PATCH 002/124] feature/AB#31658-Fix-BasicEmail: harden basicEmail
against ResizeObserver error and modal variations
---
.../Unity.AutoUI/cypress/e2e/basicEmail.cy.ts | 113 ++++++++++++++----
1 file changed, 93 insertions(+), 20 deletions(-)
diff --git a/applications/Unity.AutoUI/cypress/e2e/basicEmail.cy.ts b/applications/Unity.AutoUI/cypress/e2e/basicEmail.cy.ts
index 206f0c1bf..329f562c6 100644
--- a/applications/Unity.AutoUI/cypress/e2e/basicEmail.cy.ts
+++ b/applications/Unity.AutoUI/cypress/e2e/basicEmail.cy.ts
@@ -1,3 +1,5 @@
+// cypress/e2e/basicEmail.cy.ts
+
describe('Send an email', () => {
const TEST_EMAIL_TO = 'grantmanagementsupport@gov.bc.ca'
const TEST_EMAIL_CC = 'UnitySupport@gov.bc.ca'
@@ -5,6 +7,16 @@ describe('Send an email', () => {
const TEMPLATE_NAME = 'Test Case 1'
const STANDARD_TIMEOUT = 20000
+ // Only suppress the noisy ResizeObserver error that Unity throws in TEST.
+ // Everything else should still fail the test.
+ Cypress.on('uncaught:exception', (err) => {
+ const msg = err && err.message ? err.message : ''
+ if (msg.indexOf('ResizeObserver loop limit exceeded') >= 0) {
+ return false
+ }
+ return true
+ })
+
const now = new Date()
const timestamp =
now.getFullYear() +
@@ -23,8 +35,6 @@ describe('Send an email', () => {
function switchToDefaultGrantsProgramIfAvailable() {
cy.get('body').then(($body) => {
- // If we are already on GrantPrograms (or can navigate there), try. Otherwise skip quietly.
- // Key point: never .should() an optional element.
const hasUserInitials = $body.find('.unity-user-initials').length > 0
if (!hasUserInitials) {
@@ -41,7 +51,6 @@ describe('Send an email', () => {
if (switchLink.length === 0) {
cy.log('Skipping tenant switch: "Switch Grant Programs" not present for this user/session')
- // Close dropdown so it does not block clicks later
cy.get('body').click(0, 0)
return
}
@@ -74,6 +83,81 @@ describe('Send an email', () => {
})
}
+ function openSavedEmailFromHistoryBySubject(subject: string) {
+ cy.get('body', { timeout: STANDARD_TIMEOUT }).then(($body) => {
+ const historyTableById = $body.find('#EmailHistoryTable')
+ if (historyTableById.length > 0) {
+ cy.get('#EmailHistoryTable', { timeout: STANDARD_TIMEOUT })
+ .should('be.visible')
+ .within(() => {
+ cy.contains('td', subject, { timeout: STANDARD_TIMEOUT })
+ .should('exist')
+ .click()
+ })
+ return
+ }
+
+ // Fallback: find the subject anywhere in a TD (scoped to avoid brittle class names)
+ cy.contains('td', subject, { timeout: STANDARD_TIMEOUT })
+ .should('exist')
+ .click()
+ })
+ }
+
+ function confirmSendDialogIfPresent() {
+ // Wait until either a bootstrap modal is shown, or SweetAlert container appears, or confirm button exists.
+ cy.get('body', { timeout: STANDARD_TIMEOUT }).should(($b) => {
+ const hasBootstrapShownModal = $b.find('.modal.show').length > 0
+ const hasSwal = $b.find('.swal2-container').length > 0
+ const hasConfirmBtn = $b.find('#btn-confirm-send').length > 0
+ expect(hasBootstrapShownModal || hasSwal || hasConfirmBtn).to.eq(true)
+ })
+
+ cy.get('body', { timeout: STANDARD_TIMEOUT }).then(($b) => {
+ const hasSwal = $b.find('.swal2-container').length > 0
+ if (hasSwal) {
+ // SweetAlert2 style
+ cy.get('.swal2-container', { timeout: STANDARD_TIMEOUT }).should('be.visible')
+ cy.contains('.swal2-container', 'Are you sure', { timeout: STANDARD_TIMEOUT }).should('exist')
+
+ // Typical confirm button class, with fallback to text match
+ if ($b.find('.swal2-confirm').length > 0) {
+ cy.get('.swal2-confirm', { timeout: STANDARD_TIMEOUT }).should('be.visible').click()
+ } else {
+ cy.contains('.swal2-container button', 'Yes', { timeout: STANDARD_TIMEOUT }).click()
+ }
+ return
+ }
+
+ const hasBootstrapShownModal = $b.find('.modal.show').length > 0
+ if (hasBootstrapShownModal) {
+ // Bootstrap modal: assert the shown modal, not the inner content div
+ cy.get('.modal.show', { timeout: STANDARD_TIMEOUT })
+ .should('be.visible')
+ .within(() => {
+ cy.contains('Are you sure you want to send this email?', { timeout: STANDARD_TIMEOUT })
+ .should('exist')
+
+ // Prefer the known id if present, otherwise click a button with expected intent text
+ if (Cypress.$('#btn-confirm-send').length > 0) {
+ cy.get('#btn-confirm-send', { timeout: STANDARD_TIMEOUT })
+ .should('exist')
+ .should('be.visible')
+ .click()
+ } else {
+ cy.contains('button', 'Confirm', { timeout: STANDARD_TIMEOUT }).click()
+ }
+ })
+ return
+ }
+
+ // Last resort: confirm button exists but modal might not be "visible" by Cypress standards
+ cy.get('#btn-confirm-send', { timeout: STANDARD_TIMEOUT })
+ .should('exist')
+ .click({ force: true })
+ })
+ }
+
it('Login', () => {
cy.login()
})
@@ -189,38 +273,27 @@ describe('Send an email', () => {
})
it('Select saved email from Email History', () => {
- cy.contains('td.data-table-header', TEST_EMAIL_SUBJECT, { timeout: STANDARD_TIMEOUT })
- .should('exist')
- .click()
+ openSavedEmailFromHistoryBySubject(TEST_EMAIL_SUBJECT)
cy.get('#EmailTo', { timeout: STANDARD_TIMEOUT }).should('be.visible')
cy.get('#EmailCC').should('be.visible')
cy.get('#EmailBCC').should('be.visible')
cy.get('#EmailSubject').should('be.visible')
- cy.get('#btn-send').should('be.visible')
- cy.get('#btn-save').should('be.visible')
+ cy.get('#btn-send', { timeout: STANDARD_TIMEOUT }).should('exist')
+ cy.get('#btn-save', { timeout: STANDARD_TIMEOUT }).should('exist')
})
it('Send the email', () => {
cy.get('#btn-send', { timeout: STANDARD_TIMEOUT })
.should('exist')
.should('be.visible')
+ .should('not.be.disabled')
.click()
})
- it('Confirm send email in modal', () => {
- cy.get('#modal-content', { timeout: STANDARD_TIMEOUT })
- .should('exist')
- .should('be.visible')
-
- cy.contains('Are you sure you want to send this email?', { timeout: STANDARD_TIMEOUT })
- .should('exist')
-
- cy.get('#btn-confirm-send', { timeout: STANDARD_TIMEOUT })
- .should('exist')
- .should('be.visible')
- .click()
+ it('Confirm send email in dialog', () => {
+ confirmSendDialogIfPresent()
})
it('Verify Logout', () => {
From 57d8bf888e49217ad1460748c39e33ec7b6f1066 Mon Sep 17 00:00:00 2001
From: aurelio-aot
Date: Thu, 22 Jan 2026 20:22:51 -0800
Subject: [PATCH 003/124] AB#29181: Block Javascript using HtmlAnalyzer and
form.io noeval
---
.../Pages/GrantApplications/Details.cshtml.cs | 138 +++++++++++++++++-
.../Pages/GrantApplications/Details.js | 8 +
.../Unity.GrantManager.Web.csproj | 1 +
3 files changed, 145 insertions(+), 2 deletions(-)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs
index 6c5419e89..0ed6352b3 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Authorization;
+using Ganss.Xss;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
@@ -144,7 +145,7 @@ public async Task OnGetAsync()
RenderFormIoToHtml = applicationForm.RenderFormIoToHtml;
if (!string.IsNullOrEmpty(applicationFormSubmission.RenderedHTML) && RenderFormIoToHtml)
{
- ApplicationFormSubmissionHtml = applicationFormSubmission.RenderedHTML;
+ ApplicationFormSubmissionHtml = SanitizeFormIoHtml(applicationFormSubmission.RenderedHTML);
}
else
{
@@ -157,6 +158,139 @@ public async Task OnPostAsync()
await Task.CompletedTask;
return Page();
}
+
+ private static string SanitizeFormIoHtml(string? html)
+ {
+ if (string.IsNullOrWhiteSpace(html))
+ {
+ return string.Empty;
+ }
+
+ var sanitizer = CreateFormIoSanitizer();
+ return sanitizer.Sanitize(html);
+ }
+
+ private static HtmlSanitizer CreateFormIoSanitizer()
+ {
+ var sanitizer = new HtmlSanitizer();
+
+ sanitizer.AllowedTags.Clear();
+ sanitizer.AllowedTags.UnionWith(new[]
+ {
+ "a", "abbr", "b", "blockquote", "br", "code", "dd", "div", "dl", "dt",
+ "em", "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
+ "i", "img", "input", "label", "legend", "li", "ol", "option", "p",
+ "pre", "select", "small", "span", "strong", "table", "tbody", "td",
+ "textarea", "tfoot", "th", "thead", "tr", "u", "ul", "button",
+ // Form.io required tags
+ "canvas", "cite", "del", "details", "ins", "kbd", "mark", "q", "s", "samp",
+ "section", "sub", "summary", "sup", "time", "var",
+ // SVG for icons (CRITICAL)
+ "svg", "path", "circle", "rect", "line", "polygon", "polyline", "g",
+ "defs", "use", "symbol", "ellipse"
+ });
+
+ sanitizer.AllowedAttributes.Clear();
+ sanitizer.AllowedAttributes.UnionWith(new[]
+ {
+ "id", "class", "name", "value", "type", "placeholder", "title", "alt",
+ "href", "src", "for", "role", "tabindex", "aria-*", "data-*",
+ "checked", "selected", "disabled", "readonly", "required", "multiple",
+ "min", "max", "step", "maxlength", "minlength", "size", "pattern", "style",
+ "colspan", "rowspan", "scope", "accept", "autocomplete", "target",
+ "rel", "download",
+ // Form sizing and structure
+ "rows", "cols", "width", "height", "open", "hidden", "datetime", "align", "valign",
+ // SVG attributes (CRITICAL for icons)
+ "viewBox", "xmlns", "fill", "stroke", "stroke-width", "d", "cx", "cy", "r",
+ "x", "y", "x1", "y1", "x2", "y2", "points", "transform"
+ });
+
+ sanitizer.AllowedSchemes.Clear();
+ sanitizer.AllowedSchemes.UnionWith(new[] { "http", "https", "mailto", "tel", "data" });
+
+ sanitizer.AllowedCssProperties.Clear();
+ sanitizer.AllowedCssProperties.UnionWith(new[]
+ {
+ "align-content",
+ "align-items",
+ "align-self",
+ "background",
+ "background-color",
+ "border",
+ "border-bottom",
+ "border-bottom-color",
+ "border-bottom-style",
+ "border-bottom-width",
+ "border-color",
+ "border-left",
+ "border-left-color",
+ "border-left-style",
+ "border-left-width",
+ "border-radius",
+ "border-right",
+ "border-right-color",
+ "border-right-style",
+ "border-right-width",
+ "border-style",
+ "border-top",
+ "border-top-color",
+ "border-top-style",
+ "border-top-width",
+ "border-width",
+ "box-shadow",
+ "box-sizing",
+ "color",
+ "column-gap",
+ "cursor",
+ "display",
+ "flex",
+ "flex-basis",
+ "flex-direction",
+ "flex-grow",
+ "flex-shrink",
+ "flex-wrap",
+ "font",
+ "font-family",
+ "font-size",
+ "font-style",
+ "font-weight",
+ "gap",
+ "height",
+ "justify-content",
+ "line-height",
+ "margin",
+ "margin-bottom",
+ "margin-left",
+ "margin-right",
+ "margin-top",
+ "max-height",
+ "max-width",
+ "min-height",
+ "min-width",
+ "padding",
+ "padding-bottom",
+ "padding-left",
+ "padding-right",
+ "padding-top",
+ "text-align",
+ "text-decoration",
+ "text-transform",
+ "vertical-align",
+ "white-space",
+ "width",
+ "word-break",
+ "word-wrap",
+ // Critical layout and positioning
+ "background-image", "background-position", "background-repeat", "background-size",
+ "bottom", "float", "left", "letter-spacing", "list-style", "list-style-type",
+ "opacity", "outline", "outline-color", "outline-style", "outline-width",
+ "overflow", "overflow-x", "overflow-y", "position", "right", "text-indent",
+ "text-overflow", "top", "visibility", "word-spacing", "z-index"
+ });
+
+ return sanitizer;
+ }
}
public class BoundWorksheet
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
index ae33c1268..b06c01627 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
@@ -90,6 +90,13 @@ $(function () {
}
Formio.icons = 'fontawesome';
+ const evaluator =
+ (Formio.Utils && Formio.Utils.Evaluator) ||
+ Formio.Evaluator;
+ if (evaluator) {
+ evaluator.noeval = true;
+ evaluator.protectedEval = true;
+ }
await Formio.createForm(
document.getElementById('formio'),
@@ -98,6 +105,7 @@ $(function () {
readOnly: true,
renderMode: 'form',
flatten: true,
+ noeval: true,
}
).then(function (form) {
handleForm(form, submissionData);
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj
index ed6b53ae7..90f221186 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Unity.GrantManager.Web.csproj
@@ -56,6 +56,7 @@
+
all
From 64c5e4fea1d9b683e09d1696f09eb4e63a311add Mon Sep 17 00:00:00 2001
From: aurelio-aot
Date: Fri, 23 Jan 2026 00:11:03 -0800
Subject: [PATCH 004/124] AB#29181: Optimize HtmlSanitizer
---
.../Pages/GrantApplications/Details.cshtml.cs | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs
index 0ed6352b3..c2753f429 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.cshtml.cs
@@ -88,6 +88,8 @@ public class DetailsModel : AbpPageModel
[BindProperty]
public HashSet ZoneStateSet { get; set; } = [];
+ private static readonly HtmlSanitizer Sanitizer = CreateFormIoSanitizer();
+
public DetailsModel(
GrantApplicationAppService grantApplicationAppService,
IWorksheetLinkAppService worksheetLinkAppService,
@@ -165,9 +167,8 @@ private static string SanitizeFormIoHtml(string? html)
{
return string.Empty;
}
-
- var sanitizer = CreateFormIoSanitizer();
- return sanitizer.Sanitize(html);
+
+ return Sanitizer.Sanitize(html);
}
private static HtmlSanitizer CreateFormIoSanitizer()
@@ -181,7 +182,7 @@ private static HtmlSanitizer CreateFormIoSanitizer()
"em", "fieldset", "form", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
"i", "img", "input", "label", "legend", "li", "ol", "option", "p",
"pre", "select", "small", "span", "strong", "table", "tbody", "td",
- "textarea", "tfoot", "th", "thead", "tr", "u", "ul", "button",
+ "textarea", "tfoot", "th", "thead", "tr", "u", "ul", "button","style",
// Form.io required tags
"canvas", "cite", "del", "details", "ins", "kbd", "mark", "q", "s", "samp",
"section", "sub", "summary", "sup", "time", "var",
@@ -198,7 +199,7 @@ private static HtmlSanitizer CreateFormIoSanitizer()
"checked", "selected", "disabled", "readonly", "required", "multiple",
"min", "max", "step", "maxlength", "minlength", "size", "pattern", "style",
"colspan", "rowspan", "scope", "accept", "autocomplete", "target",
- "rel", "download",
+ "rel", "download","ref", "key", "lang", "dir",
// Form sizing and structure
"rows", "cols", "width", "height", "open", "hidden", "datetime", "align", "valign",
// SVG attributes (CRITICAL for icons)
@@ -281,6 +282,7 @@ private static HtmlSanitizer CreateFormIoSanitizer()
"width",
"word-break",
"word-wrap",
+ "page-break-after", "page-break-before", "page-break-inside", "table-layout",
// Critical layout and positioning
"background-image", "background-position", "background-repeat", "background-size",
"bottom", "float", "left", "letter-spacing", "list-style", "list-style-type",
From bfbf84bb5527ead9adc476633c48b61f92dc0c97 Mon Sep 17 00:00:00 2001
From: aurelio-aot
Date: Fri, 23 Jan 2026 03:15:32 -0800
Subject: [PATCH 005/124] AB#29181: Prevent CSS leak into CHEFS submission form
using shadow DOM
---
.../GrantApplications/Details-shadow-dom.css | 106 ++++++++++++++++++
.../Pages/GrantApplications/Details.js | 97 ++++++++++++++--
2 files changed, 192 insertions(+), 11 deletions(-)
create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details-shadow-dom.css
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details-shadow-dom.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details-shadow-dom.css
new file mode 100644
index 000000000..66bd09970
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details-shadow-dom.css
@@ -0,0 +1,106 @@
+/*
+ * Details-shadow-dom.css
+ * CSS rules for form.io submission rendering inside Shadow DOM
+ * This file is loaded inside the shadow root to style form.io content
+ */
+
+/* ===== Card Accordion Styling ===== */
+.card-header {
+ padding-bottom: 1.5rem !important;
+}
+
+.card-title {
+ font-weight: 600 !important;
+}
+
+/* Accordion arrow icon (collapsed state) */
+.card-header:not(.card-body .card-header)::before {
+ content: ' ';
+ display: block;
+ border-bottom: 1px solid #313132;
+ border-right: 1px solid #313132;
+ height: 10px;
+ width: 10px;
+ transform: rotate(45deg);
+ float: right !important;
+ transition: all 0.5s !important;
+}
+
+/* Accordion arrow icon (expanded state) */
+.card-header:not(.card-body .card-header).custom-active::before {
+ content: ' ';
+ display: block;
+ border-bottom: 1px solid #313132;
+ border-right: 1px solid #313132;
+ height: 10px;
+ width: 10px;
+ transform: rotate(-135deg);
+ float: right !important;
+ transition: all 0.5s !important;
+}
+
+/* Accordion body animation */
+.card-body {
+ transition: height 0.5s;
+ overflow: hidden;
+}
+
+.card-header.bg-default {
+ background-color: white !important;
+ margin-top: 1.2rem;
+}
+
+/* ===== Form.io Component Styling ===== */
+
+/* Bold labels in form.io components */
+.formio-component label {
+ font-weight: bold !important;
+}
+
+/* Hide nextTab components */
+[class^='formio-component-nextTab'] {
+ display: none;
+}
+
+/* Show textarea card bodies (override default hiding) */
+.formio-component-textarea div .card-body {
+ display: inherit !important;
+}
+
+/* Hide submit button */
+.formio-component-submit {
+ display: none;
+}
+
+/* Word break for editor content */
+.formio-editor-read-only-content {
+ word-break: normal !important;
+}
+
+/* ===== Disable Interactivity (Read-Only Mode) ===== */
+
+/* Disable all links */
+a {
+ pointer-events: none;
+ cursor: default;
+ text-decoration: none;
+ color: black;
+}
+
+/* Hide all buttons */
+button {
+ display: none;
+}
+
+/* ===== Form Validation ===== */
+
+/* Error label styling */
+form label.error {
+ color: #dc3545 !important;
+ font-size: 0.8em;
+}
+
+/* Hide calendar icon in input groups */
+.input-group-text:has(i.fa-calendar) {
+ display: none !important;
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
index b06c01627..77f1b7694 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
@@ -50,17 +50,66 @@ $(function () {
}
}
+ function initializeShadowDOM() {
+ const formioContainer = document.getElementById('formio');
+
+ if (!formioContainer) {
+ console.error('Formio container not found');
+ return null;
+ }
+
+ // Check if shadow root already exists
+ if (formioContainer.shadowRoot) {
+ return formioContainer.shadowRoot;
+ }
+
+ // Create shadow DOM with open mode (allows external JS access)
+ const shadowRoot = formioContainer.attachShadow({mode: 'open'});
+
+ // Load form.io CSS inside shadow DOM
+ const formioStyle = document.createElement('link');
+ formioStyle.rel = 'stylesheet';
+ formioStyle.href = '/libs/formiojs/formio.form.css';
+ shadowRoot.appendChild(formioStyle);
+
+ // Load bootstrap CSS
+ const bootstrapStyle = document.createElement('link');
+ bootstrapStyle.rel = 'stylesheet';
+ bootstrapStyle.href = '/libs/bootstrap-4/dist/css/bootstrap.min.css';
+ shadowRoot.appendChild(bootstrapStyle);
+
+ // Load Details-shadow-dom.css into shadow DOM (CRITICAL for accordion, styling, etc.)
+ const detailsStyle = document.createElement('link');
+ detailsStyle.rel = 'stylesheet';
+ detailsStyle.href = '/Pages/GrantApplications/Details-shadow-dom.css';
+ shadowRoot.appendChild(detailsStyle);
+
+ return shadowRoot;
+ }
+
function renderSubmission() {
+ // Initialize shadow DOM first
+ const shadowRoot = initializeShadowDOM();
+
if (renderFormIoToHtml == 'False' || hasRenderedHtml == 'False') {
- getSubmission();
+ getSubmission(shadowRoot);
} else {
$('.spinner-grow').hide();
- addEventListeners();
+
+ // Inject pre-rendered HTML into shadow DOM
+ if (shadowRoot) {
+ const htmlContent = document.getElementById('ApplicationFormSubmissionHtml');
+ if (htmlContent && htmlContent.value) {
+ shadowRoot.innerHTML += htmlContent.value;
+ }
+ }
+
+ addEventListeners(shadowRoot);
}
}
- async function getSubmission() {
+ async function getSubmission(shadowRoot) {
try {
$('.spinner-grow').hide();
let submissionDataString = document.getElementById(
@@ -98,8 +147,13 @@ $(function () {
evaluator.protectedEval = true;
}
+ // Create container inside shadow DOM
+ const container = document.createElement('div');
+ container.id = 'formio-container';
+ shadowRoot.appendChild(container);
+
await Formio.createForm(
- document.getElementById('formio'),
+ container, // Render inside shadow DOM
formSchema,
{
readOnly: true,
@@ -108,18 +162,18 @@ $(function () {
noeval: true,
}
).then(function (form) {
- handleForm(form, submissionData);
+ handleForm(form, submissionData, shadowRoot);
});
} catch (error) {
console.error(error);
}
}
- function handleForm(form, submission) {
+ function handleForm(form, submission, shadowRoot) {
form.submission = submission;
form.resetValue();
form.refresh();
- form.on('render', addEventListeners);
+ form.on('render', () => addEventListeners(shadowRoot));
waitFor(() => isFormChanging(form)).then(() => {
setTimeout(storeRenderedHtml, 2000);
@@ -130,7 +184,9 @@ $(function () {
if (renderFormIoToHtml == 'False') {
return;
}
- let innerHTML = document.getElementById('formio').innerHTML;
+ const formioContainer = document.getElementById('formio');
+ const shadowRoot = formioContainer.shadowRoot;
+ let innerHTML = shadowRoot ? shadowRoot.innerHTML : formioContainer.innerHTML;
let submissionId = document.getElementById(
'ApplicationFormSubmissionId'
).value;
@@ -152,7 +208,7 @@ $(function () {
}
// Wait for the DOM to be fully loaded
- function addEventListeners() {
+ function addEventListeners(shadowRoot) {
const cardHeaders = getCardHeaders();
const cardBodies = getCardBodies();
@@ -170,14 +226,18 @@ $(function () {
// Get all card headers
function getCardHeaders() {
- return document.querySelectorAll(
+ const formioContainer = document.getElementById('formio');
+ const root = formioContainer.shadowRoot || document;
+ return root.querySelectorAll(
'.card-header:not(.card-body .card-header)'
);
}
// Get all card bodies
function getCardBodies() {
- return document.querySelectorAll(
+ const formioContainer = document.getElementById('formio');
+ const root = formioContainer.shadowRoot || document;
+ return root.querySelectorAll(
'.card-body:not(.card-body .card-body)'
);
}
@@ -359,10 +419,25 @@ $(function () {
formioCSS.rel = 'stylesheet';
formioCSS.href = '/libs/formiojs/formio.form.css';
+ // Add inline CSS to disable links (more reliable than JavaScript)
+ const inlineStyle = doc.createElement('style');
+ inlineStyle.textContent = `
+ a {
+ pointer-events: none !important;
+ cursor: default !important;
+ text-decoration: none !important;
+ color: black !important;
+ }
+ button {
+ display: none !important;
+ }
+ `;
+
head.appendChild(jqueryScript);
head.appendChild(formioScript);
head.appendChild(bootstrapCSS);
head.appendChild(formioCSS);
+ head.appendChild(inlineStyle);
// BODY
const body = doc.body;
From 1cd28b74032085be59f15d0e7affac688ec33262 Mon Sep 17 00:00:00 2001
From: aurelio-aot
Date: Fri, 23 Jan 2026 03:19:24 -0800
Subject: [PATCH 006/124] AB#29181: Fix sonarqube issue
---
.../Unity.GrantManager.Web/Pages/GrantApplications/Details.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
index 77f1b7694..14ca4a74c 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
@@ -140,7 +140,7 @@ $(function () {
Formio.icons = 'fontawesome';
const evaluator =
- (Formio.Utils && Formio.Utils.Evaluator) ||
+ Formio.Utils?.Evaluator ||
Formio.Evaluator;
if (evaluator) {
evaluator.noeval = true;
From 30fe646ce000cf6f662c6f4b258b06a891fc26e7 Mon Sep 17 00:00:00 2001
From: aurelio-aot <97022120+aurelio-aot@users.noreply.github.com>
Date: Fri, 23 Jan 2026 08:49:13 -0800
Subject: [PATCH 007/124] Potential fix for code scanning alert no. 81: DOM
text reinterpreted as HTML
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
---
.../Unity.GrantManager.Web/Pages/GrantApplications/Details.js | 2 +-
.../Unity.GrantManager/src/Unity.GrantManager.Web/package.json | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
index 14ca4a74c..202e8354f 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
@@ -100,7 +100,7 @@ $(function () {
if (shadowRoot) {
const htmlContent = document.getElementById('ApplicationFormSubmissionHtml');
if (htmlContent && htmlContent.value) {
- shadowRoot.innerHTML += htmlContent.value;
+ shadowRoot.innerHTML += DOMPurify.sanitize(htmlContent.value);
}
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/package.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/package.json
index b65363bc8..63cd5b81d 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/package.json
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/package.json
@@ -27,7 +27,8 @@
"sortablejs": "~1.15.6",
"tributejs": "~5.1.3",
"tinymce": "~8.3.2",
- "handlebars": "~4.7.8"
+ "handlebars": "~4.7.8",
+ "dompurify": "^3.3.1"
},
"devDependencies": {
"@types/jquery": "~3.5.33"
From a4d68cfca43b98bb8bcd493847f1169b043b2c4b Mon Sep 17 00:00:00 2001
From: Andre Goncalves
Date: Fri, 23 Jan 2026 08:56:29 -0800
Subject: [PATCH 008/124] AB#31659 update custom exception classes to modern
.NET 9
---
.../ApplicationFormSetupException.cs | 11 +------
.../InvalidCommentParametersException.cs | 19 ++----------
.../InvalidFormDataSubmissionException.cs | 31 ++++++-------------
3 files changed, 13 insertions(+), 48 deletions(-)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/ApplicationFormSetupException.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/ApplicationFormSetupException.cs
index fc1333373..2e4b9b30d 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/ApplicationFormSetupException.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/ApplicationFormSetupException.cs
@@ -1,10 +1,7 @@
-using System;
-using System.Runtime.Serialization;
-using Volo.Abp.Validation;
+using Volo.Abp.Validation;
namespace Unity.GrantManager.Exceptions
{
- [Serializable]
public class ApplicationFormSetupException : AbpValidationException
{
private const string FormSetupErrorMessage = "Application Form Setup Error";
@@ -14,11 +11,5 @@ public ApplicationFormSetupException(string? message = null)
{
LogLevel = Microsoft.Extensions.Logging.LogLevel.Error;
}
-
- protected ApplicationFormSetupException(SerializationInfo serializationEntries, StreamingContext context)
- : base([new(FormSetupErrorMessage)])
- {
- LogLevel = Microsoft.Extensions.Logging.LogLevel.Error;
- }
}
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidCommentParametersException.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidCommentParametersException.cs
index ac835604b..c44732088 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidCommentParametersException.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidCommentParametersException.cs
@@ -1,23 +1,10 @@
-using System;
-using System.Runtime.Serialization;
-using Volo.Abp.Validation;
+using Volo.Abp.Validation;
namespace Unity.GrantManager.Exceptions
{
- [Serializable]
- public class InvalidCommentParametersException : AbpValidationException
+ public class InvalidCommentParametersException(string? message = null)
+ : AbpValidationException([new(message ?? InvalidCommentMessage)])
{
private const string InvalidCommentMessage = "Invalid Comment Parameters";
-
- public InvalidCommentParametersException(string? message = null)
- : base([new(message ?? InvalidCommentMessage)])
- {
- }
-
- // Fix: Adjust the constructor to match the base class signature
- protected InvalidCommentParametersException(SerializationInfo serializationEntries, StreamingContext context)
- : base([new(InvalidCommentMessage)])
- {
- }
}
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidFormDataSubmissionException.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidFormDataSubmissionException.cs
index 27a3cab5c..a5a8f8e8e 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidFormDataSubmissionException.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Application/Exceptions/InvalidFormDataSubmissionException.cs
@@ -1,23 +1,10 @@
-using System;
-using System.Runtime.Serialization;
-using Volo.Abp.Validation;
-
-namespace Unity.GrantManager.Exceptions
-{
- [Serializable]
- public class InvalidFormDataSubmissionException : AbpValidationException
- {
- private const string InvalidCommentMessage = "Invalid Form Submission Data";
-
- public InvalidFormDataSubmissionException(string? message = null)
- : base([new(message ?? InvalidCommentMessage)])
- {
- }
-
- protected InvalidFormDataSubmissionException(SerializationInfo serializationEntries, StreamingContext context)
- : base([new(InvalidCommentMessage)])
- {
- // Deserialize additional properties if needed
- }
- }
+using Volo.Abp.Validation;
+
+namespace Unity.GrantManager.Exceptions
+{
+ public class InvalidFormDataSubmissionException(string? message = null)
+ : AbpValidationException([new(message ?? InvalidCommentMessage)])
+ {
+ private const string InvalidCommentMessage = "Invalid Form Submission Data";
+ }
}
\ No newline at end of file
From b0102eea327950e36a7b0516747d6b7ce12e44fb Mon Sep 17 00:00:00 2001
From: Andre Goncalves
Date: Fri, 23 Jan 2026 09:47:36 -0800
Subject: [PATCH 009/124] AB#31662 dbmigrator appsettings
---
applications/Unity.GrantManager/.gitignore | 3 ++
.../Unity.GrantManager.DbMigrator/README.md | 51 +++++++++++++++++++
.../appsettings.json | 8 +--
3 files changed, 58 insertions(+), 4 deletions(-)
create mode 100644 applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md
diff --git a/applications/Unity.GrantManager/.gitignore b/applications/Unity.GrantManager/.gitignore
index 226f80e88..9e44b971e 100644
--- a/applications/Unity.GrantManager/.gitignore
+++ b/applications/Unity.GrantManager/.gitignore
@@ -261,6 +261,9 @@ src/Unity.GrantManager.DbMigrator/Logs/*
src/Unity.GrantManager.Blazor.Server/Logs/*
src/Unity.GrantManager.Blazor.Server.Tiered/Logs/*
+# Development settings with sensitive information
+**/appsettings.Development.json
+
# Use abp install-libs to restore.
**/wwwroot/libs/*
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md
new file mode 100644
index 000000000..909ce676f
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/README.md
@@ -0,0 +1,51 @@
+# Unity.GrantManager.DbMigrator
+
+This project is responsible for running database migrations for the Unity Grant Manager application.
+
+## Local Development Setup
+
+The `appsettings.json` file in this project intentionally excludes sensitive information like database passwords to comply with security best practices and avoid SonarQube or Copilot warnings about checking credentials into source control.
+
+### Option 1: Using appsettings.Development.json (Recommended for Local Development)
+
+Create an `appsettings.Development.json` file in the DbMigrator project directory with your connection strings including passwords:
+
+```json
+{
+ "ConnectionStrings": {
+ "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;Password=your_password_here",
+ "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;Password=your_password_here"
+ }
+}
+```
+
+**Note:** This file is excluded from git via `.gitignore` to keep your credentials secure.
+
+### Option 2: Using User Secrets
+
+Alternatively, you can use .NET User Secrets to store sensitive configuration:
+
+1. Right-click on the `Unity.GrantManager.DbMigrator` project in Visual Studio
+2. Select "Manage User Secrets"
+3. Add your connection strings with passwords:
+
+```json
+{
+ "ConnectionStrings": {
+ "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;Password=your_password_here",
+ "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;Password=your_password_here"
+ }
+}
+```
+
+User secrets are stored outside of your project directory and are never checked into source control.
+
+## Running the Migrator
+
+Once you've configured your connection strings using either option above, you can run the migrator:
+
+```bash
+dotnet run
+```
+
+Or run it from Visual Studio by setting `Unity.GrantManager.DbMigrator` as the startup project.
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json
index b62692978..0ac487eff 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json
@@ -1,8 +1,8 @@
{
- "ConnectionStrings": {
- "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;Password=admin",
- "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;Password=admin"
- },
+"ConnectionStrings": {
+ "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;",
+ "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;"
+},
"Redis": {
"Configuration": "127.0.0.1"
},
From cca159eca7fb76e962b1429cc8be0123f619c1f3 Mon Sep 17 00:00:00 2001
From: Andre Goncalves <98196495+AndreGAot@users.noreply.github.com>
Date: Fri, 23 Jan 2026 10:02:53 -0800
Subject: [PATCH 010/124] Update
applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../src/Unity.GrantManager.DbMigrator/appsettings.json | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json
index 0ac487eff..626191b07 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.DbMigrator/appsettings.json
@@ -1,8 +1,8 @@
{
-"ConnectionStrings": {
- "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;",
- "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;"
-},
+ "ConnectionStrings": {
+ "Default": "Host=localhost;port=5432;Database=UnityGrantManager;Username=postgres;",
+ "Tenant": "Host=localhost;port=5432;Database=UnityGrantTenant;Username=postgres;"
+ },
"Redis": {
"Configuration": "127.0.0.1"
},
From 6740d62ef4f3e0cad0fd8b43d9f1deb52ff3e554 Mon Sep 17 00:00:00 2001
From: Andre Goncalves
Date: Fri, 23 Jan 2026 10:27:12 -0800
Subject: [PATCH 011/124] AB#31665 sonarqube cleanup
---
.../Security/FakeCurrentPrincipalAccessor.cs | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Security/FakeCurrentPrincipalAccessor.cs b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Security/FakeCurrentPrincipalAccessor.cs
index 29e21fc53..74bf6c694 100644
--- a/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Security/FakeCurrentPrincipalAccessor.cs
+++ b/applications/Unity.GrantManager/modules/Unity.Flex/test/Unity.Flex.TestBase/Security/FakeCurrentPrincipalAccessor.cs
@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-using System.Security.Claims;
+using System.Security.Claims;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
@@ -13,14 +12,14 @@ protected override ClaimsPrincipal GetClaimsPrincipal()
return GetPrincipal();
}
- private ClaimsPrincipal GetPrincipal()
+ private static ClaimsPrincipal GetPrincipal()
{
- return new ClaimsPrincipal(new ClaimsIdentity(new List
- {
+ return new ClaimsPrincipal(new ClaimsIdentity(
+ [
new Claim(AbpClaimTypes.UserId, "2e701e62-0953-4dd3-910b-dc6cc93ccb0d"),
new Claim(AbpClaimTypes.UserName, "admin"),
new Claim(AbpClaimTypes.Email, "admin@abp.io")
- }
+ ]
)
);
}
From 113f9bddcd88dee923eeb0979433b8abfb5fc194 Mon Sep 17 00:00:00 2001
From: Patrick <135162612+plavoie-BC@users.noreply.github.com>
Date: Fri, 23 Jan 2026 10:53:14 -0800
Subject: [PATCH 012/124] AB#31140 - Add support for default DataTable ordering
by column name
---
.../Pages/PaymentRequests/Index.js | 5 +-
.../Shared/Components/PaymentInfo/Default.js | 5 +-
.../wwwroot/themes/ux2/table-utils.js | 51 ++++++++++++++++++-
.../Pages/Applicants/Index.js | 5 +-
.../Pages/GrantApplications/Index.js | 5 +-
5 files changed, 65 insertions(+), 6 deletions(-)
diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js
index 2fd4a5f98..5b7aff6ba 100644
--- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js
+++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Pages/PaymentRequests/Index.js
@@ -151,7 +151,10 @@ $(function () {
defaultVisibleColumns,
listColumns,
maxRowsPerPage: 10,
- defaultSortColumn: 13,
+ defaultSortColumn: {
+ name: 'requestedOn',
+ dir: 'desc'
+ },
dataEndpoint: unity.payments.paymentRequests.paymentRequest.getList,
data: {},
responseCallback,
diff --git a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js
index 3cceaffd6..56cd6f0cb 100644
--- a/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js
+++ b/applications/Unity.GrantManager/modules/Unity.Payments/src/Unity.Payments.Web/Views/Shared/Components/PaymentInfo/Default.js
@@ -198,7 +198,10 @@
defaultVisibleColumns,
listColumns,
maxRowsPerPage: 10,
- defaultSortColumn: 3,
+ defaultSortColumn: {
+ name: 'requestedOn',
+ dir: 'desc'
+ },
dataEndpoint:
unity.payments.paymentRequests.paymentRequest
.getListByApplicationId,
diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js
index d0fa628e3..745fec847 100644
--- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js
+++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/table-utils.js
@@ -221,6 +221,7 @@ function initializeDataTable(options) {
// Process columns and visibility
let tableColumns = assignColumnIndices(listColumns);
let visibleColumns = getVisibleColumnIndexes(tableColumns, defaultVisibleColumns);
+ let defaultSortOrder = getDefaultSortOrder(tableColumns, defaultSortColumn);
// Prepare action buttons
let updatedActionButtons = prepareActionButtons(actionButtons, useNullPlaceholder, disableColumnSelect);
@@ -235,7 +236,7 @@ function initializeDataTable(options) {
let iDt = new DataTable(dt, {
serverSide: serverSideEnabled,
paging: pagingEnabled,
- order: [[defaultSortColumn, 'desc']],
+ order: defaultSortOrder,
searching: true,
scrollX: true,
scrollCollapse: true,
@@ -650,7 +651,6 @@ function addDataTableFixCSS() {
}
}
-
/**
* Assigns sequential index values to columns that don't have one.
* Preserves existing indices and continues numbering from the highest existing index.
@@ -854,3 +854,50 @@ $(document).keydown(function (e) {
}
});
+/**
+ * Resolves a column index by its name or data field.
+ * @param {Array