case 4717 - KPI interval accepted only as a known date unit
Six KPIs put the request's interval criterion into the query as
DATE_TRUNC('<interval>', ...) unchecked, reachable by staff down to
TechRestricted (security review F26). New KPIFetcher.DateTruncUnitOrNull
accepts only PostgreSQL's date_trunc unit names (any case); the seven KPIs that
read interval refuse anything else with "Invalid 'interval' criteria", the same
form as their existing missing-criteria errors. The client only sends day or
month. (WorkOrderByStatusList reads interval without using it; checked too.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -183,7 +183,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s
|
|||||||
| 4713 | Contact users limited to customer roles | F28 |
|
| 4713 | Contact users limited to customer roles | F28 |
|
||||||
| 4715 | Download file names checked (one helper) | F21. **Done** (`FileUtil.FullPathInFolderOrNull` in temp/backup exists checks and both log routes; test `DownloadNames`) |
|
| 4715 | Download file names checked (one helper) | F21. **Done** (`FileUtil.FullPathInFolderOrNull` in temp/backup exists checks and both log routes; test `DownloadNames`) |
|
||||||
| 4716 | Pick-list search text as SQL parameters | F18. **Done** (`@plq`/`@pltag` parameters; test `FetchUserPickListAutoCompleteApostrophe` fails on the old code, passes now; 9/9 pick-list tests) |
|
| 4716 | Pick-list search text as SQL parameters | F18. **Done** (`@plq`/`@pltag` parameters; test `FetchUserPickListAutoCompleteApostrophe` fails on the old code, passes now; 9/9 pick-list tests) |
|
||||||
| 4717 | KPI interval allowlist | F26 |
|
| 4717 | KPI interval allowlist | F26. **Done** (`KPIFetcher.DateTruncUnitOrNull` in the 7 KPIs reading `interval`; test `KpiInterval`) |
|
||||||
| 4718 | Report and translation import need create rights | F19, F24. **Done** (contact and Service 403, BizAdmin 202; test `ImportRights`) |
|
| 4718 | Report and translation import need create rights | F19, F24. **Done** (contact and Service 403, BizAdmin 202; test `ImportRights`) |
|
||||||
| 4719 | Report page network restricted; notification reports not run as SuperUser | F20 |
|
| 4719 | Report page network restricted; notification reports not run as SuperUser | F20 |
|
||||||
| 4720 | Restricted roles filtered in report/export data | F27 |
|
| 4720 | Restricted roles filtered in report/export data | F27 |
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ take the next free number; a fixed or accepted finding keeps its ID and changes
|
|||||||
| F19 | Critical | **fixed in case 4718** | Any logged-in user, customer contacts included, can import report templates, whose JavaScript runs in the server's browser |
|
| F19 | Critical | **fixed in case 4718** | Any logged-in user, customer contacts included, can import report templates, whose JavaScript runs in the server's browser |
|
||||||
| F21 | Critical | **fixed in case 4715** | Download file names from the URL are not checked: `report/download` and `export/download` may serve files outside the temp folder on Windows |
|
| F21 | Critical | **fixed in case 4715** | Download file names from the URL are not checked: `report/download` and `export/download` may serve files outside the temp folder on Windows |
|
||||||
| F18 | Critical | **fixed in case 4716** | Pick-list search text is put into SQL without escaping; any logged-in user can reach it |
|
| F18 | Critical | **fixed in case 4716** | Pick-list search text is put into SQL without escaping; any logged-in user can reach it |
|
||||||
| F26 | Critical | confirmed (code) | KPI `interval` criterion is put into SQL unchecked; reachable by staff users down to TechRestricted |
|
| F26 | Critical | **fixed in case 4717** | KPI `interval` criterion is put into SQL unchecked; reachable by staff users down to TechRestricted |
|
||||||
| F13 | High | **confirmed** | Customer contacts can open any customer's work order (customer view, customer report, attachments) by id |
|
| F13 | High | **confirmed** | Customer contacts can open any customer's work order (customer view, customer report, attachments) by id |
|
||||||
| F14 | High | **confirmed** | Customer contacts can read, and by code change, delete and create, any customer's service requests |
|
| F14 | High | **confirmed** | Customer contacts can read, and by code change, delete and create, any customer's service requests |
|
||||||
| F15 | High | **confirmed** | Customer contacts can list every customer (and vendors, users, parts...) with any pick-list columns, phones and emails included |
|
| F15 | High | **confirmed** | Customer contacts can list every customer (and vendors, users, parts...) with any pick-list columns, phones and emails included |
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ namespace AyaNova.KPI
|
|||||||
internal static class KPIFetcher
|
internal static class KPIFetcher
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//case 4717: the date_trunc field names PostgreSQL accepts; anything else in a KPI interval is refused
|
||||||
|
private static readonly System.Collections.Generic.HashSet<string> DateTruncUnits = new System.Collections.Generic.HashSet<string> {
|
||||||
|
"microseconds", "milliseconds", "second", "minute", "hour", "day", "week",
|
||||||
|
"month", "quarter", "year", "decade", "century", "millennium" };
|
||||||
|
|
||||||
|
internal static string DateTruncUnitOrNull(string unit)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(unit))
|
||||||
|
return null;
|
||||||
|
unit = unit.Trim().ToLowerInvariant();
|
||||||
|
return DateTruncUnits.Contains(unit) ? unit : null;
|
||||||
|
}
|
||||||
|
|
||||||
#if (AYSHOWKPIQUERYINFO)
|
#if (AYSHOWKPIQUERYINFO)
|
||||||
#if (DEBUG)
|
#if (DEBUG)
|
||||||
#warning FYI AYSHOWKPIQUERYINFO is defined
|
#warning FYI AYSHOWKPIQUERYINFO is defined
|
||||||
|
|||||||
@@ -55,7 +55,13 @@ namespace AyaNova.KPI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var timeSpan = (string)options.Criteria["timeSpan"];
|
var timeSpan = (string)options.Criteria["timeSpan"];
|
||||||
var interval = (string)options.Criteria["interval"];
|
//case 4717: interval goes into DATE_TRUNC in the query, so only PostgreSQL's own unit names are accepted
|
||||||
|
var interval = KPIFetcher.DateTruncUnitOrNull((string)options.Criteria["interval"]);
|
||||||
|
if (interval == null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Invalid 'interval' criteria";
|
||||||
|
return;
|
||||||
|
}
|
||||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||||
|
|
||||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||||
|
|||||||
@@ -41,7 +41,13 @@ AuthorizationRoles.Accounting;
|
|||||||
}
|
}
|
||||||
|
|
||||||
var timeSpan = (string)options.Criteria["timeSpan"];
|
var timeSpan = (string)options.Criteria["timeSpan"];
|
||||||
var interval = (string)options.Criteria["interval"];
|
//case 4717: interval goes into DATE_TRUNC in the query, so only PostgreSQL's own unit names are accepted
|
||||||
|
var interval = KPIFetcher.DateTruncUnitOrNull((string)options.Criteria["interval"]);
|
||||||
|
if (interval == null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Invalid 'interval' criteria";
|
||||||
|
return;
|
||||||
|
}
|
||||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||||
|
|||||||
@@ -41,7 +41,13 @@ AuthorizationRoles.Accounting;
|
|||||||
}
|
}
|
||||||
|
|
||||||
var timeSpan = (string)options.Criteria["timeSpan"];
|
var timeSpan = (string)options.Criteria["timeSpan"];
|
||||||
var interval = (string)options.Criteria["interval"];
|
//case 4717: interval goes into DATE_TRUNC in the query, so only PostgreSQL's own unit names are accepted
|
||||||
|
var interval = KPIFetcher.DateTruncUnitOrNull((string)options.Criteria["interval"]);
|
||||||
|
if (interval == null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Invalid 'interval' criteria";
|
||||||
|
return;
|
||||||
|
}
|
||||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||||
|
|||||||
@@ -42,7 +42,13 @@ AuthorizationRoles.Accounting;
|
|||||||
userWhere = $"AND aworkorderitemlabor.userid = {userid}";
|
userWhere = $"AND aworkorderitemlabor.userid = {userid}";
|
||||||
}
|
}
|
||||||
var timeSpan = (string)options.Criteria["timeSpan"];
|
var timeSpan = (string)options.Criteria["timeSpan"];
|
||||||
var interval = (string)options.Criteria["interval"];
|
//case 4717: interval goes into DATE_TRUNC in the query, so only PostgreSQL's own unit names are accepted
|
||||||
|
var interval = KPIFetcher.DateTruncUnitOrNull((string)options.Criteria["interval"]);
|
||||||
|
if (interval == null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Invalid 'interval' criteria";
|
||||||
|
return;
|
||||||
|
}
|
||||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorderitemlabor.servicestopdate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorderitemlabor.servicestopdate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||||
var techtags = options.Criteria["techtags"].ToObject<List<string>>();
|
var techtags = options.Criteria["techtags"].ToObject<List<string>>();
|
||||||
bool techtagsany = options.Criteria["techtagsany"].ToObject<bool>();
|
bool techtagsany = options.Criteria["techtagsany"].ToObject<bool>();
|
||||||
|
|||||||
@@ -34,7 +34,13 @@ namespace AyaNova.KPI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var timeSpan = (string)options.Criteria["timeSpan"];
|
var timeSpan = (string)options.Criteria["timeSpan"];
|
||||||
var interval = (string)options.Criteria["interval"];
|
//case 4717: interval goes into DATE_TRUNC in the query, so only PostgreSQL's own unit names are accepted
|
||||||
|
var interval = KPIFetcher.DateTruncUnitOrNull((string)options.Criteria["interval"]);
|
||||||
|
if (interval == null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Invalid 'interval' criteria";
|
||||||
|
return;
|
||||||
|
}
|
||||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorderitemlabor.servicestopdate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorderitemlabor.servicestopdate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||||
|
|||||||
@@ -41,7 +41,13 @@ namespace AyaNova.KPI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var timeSpan = (string)options.Criteria["timeSpan"];
|
var timeSpan = (string)options.Criteria["timeSpan"];
|
||||||
var interval = (string)options.Criteria["interval"];
|
//case 4717: interval goes into DATE_TRUNC in the query, so only PostgreSQL's own unit names are accepted
|
||||||
|
var interval = KPIFetcher.DateTruncUnitOrNull((string)options.Criteria["interval"]);
|
||||||
|
if (interval == null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Invalid 'interval' criteria";
|
||||||
|
return;
|
||||||
|
}
|
||||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||||
|
|||||||
@@ -41,7 +41,13 @@ namespace AyaNova.KPI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var timeSpan = (string)options.Criteria["timeSpan"];
|
var timeSpan = (string)options.Criteria["timeSpan"];
|
||||||
var interval = (string)options.Criteria["interval"];
|
//case 4717: interval goes into DATE_TRUNC in the query, so only PostgreSQL's own unit names are accepted
|
||||||
|
var interval = KPIFetcher.DateTruncUnitOrNull((string)options.Criteria["interval"]);
|
||||||
|
if (interval == null)
|
||||||
|
{
|
||||||
|
_errorMessage = "Invalid 'interval' criteria";
|
||||||
|
return;
|
||||||
|
}
|
||||||
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
var dateWhere = DataListSqlFilterCriteriaBuilder.DataFilterToColumnCriteria("aworkorder.createddate", UiFieldDataType.DateTime, "no-operator", timeSpan, options.ClientTimeStamp);
|
||||||
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
var wotags = options.Criteria["wotags"].ToObject<List<string>>();
|
||||||
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
bool wotagsany = options.Criteria["wotagsany"].ToObject<bool>();
|
||||||
|
|||||||
Reference in New Issue
Block a user