Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/candidate-9.2.x' into candidate-…
Browse files Browse the repository at this point in the history
…9.4.x

Signed-off-by: Gavin Halliday <[email protected]>

# Conflicts:
#	helm/hpcc/Chart.yaml
#	helm/hpcc/templates/_helpers.tpl
#	version.cmake
  • Loading branch information
ghalliday committed Dec 20, 2024
2 parents d99e2d5 + 0b7881c commit be62432
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 33 deletions.
6 changes: 5 additions & 1 deletion esp/services/ws_workunits/ws_workunitsHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4469,7 +4469,11 @@ void CWsWuFileHelper::zipZAPFiles(const char* parentFolder, const char* zapFiles
else
zipCommand.setf("cd %s\nzip -r", parentFolder);
if (!isEmptyString(passwordReq))
zipCommand.append(" --password ").append(passwordReq);
{
StringBuffer sanitizedPassword;
sanitizeCommandArg(passwordReq, sanitizedPassword);
zipCommand.append(" --password ").append(sanitizedPassword);
}
zipCommand.append(" ").append(zipFileNameWithFullPath).append(" ").append(zapFiles);
int zipRet = system(zipCommand);
if (zipRet != 0)
Expand Down
2 changes: 1 addition & 1 deletion esp/src/src-react/components/Logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const Logs: React.FunctionComponent<LogsProps> = ({

const now = React.useMemo(() => new Date(), []);

const { columns: logColumns } = useLogAccessInfo();
const { logsColumns: logColumns } = useLogAccessInfo();

// Grid ---
const columns = React.useMemo((): FluentColumns => {
Expand Down
12 changes: 4 additions & 8 deletions esp/src/src-react/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as React from "react";
import { IconButton, IContextualMenuItem, INavLink, INavLinkGroup, Link, mergeStyleSets, Nav, Stack } from "@fluentui/react";
import { useConst } from "@fluentui/react-hooks";
import nlsHPCC from "src/nlsHPCC";
import { hasLogAccess } from "src/ESPLog";
import { containerized, bare_metal } from "src/BuildInfo";
import { navCategory } from "../util/history";
import { MainNav, routes } from "../routes";
import { useFavorite, useFavorites, useHistory } from "../hooks/favorite";
import { useLogAccessInfo } from "../hooks/platform";
import { useSessionStore } from "../hooks/store";
import { useUserTheme } from "../hooks/theme";
import { useMyAccount } from "../hooks/user";
Expand Down Expand Up @@ -295,12 +295,7 @@ export const SubNavigation: React.FunctionComponent<SubNavigationProps> = ({
}
}), [theme]);

const [logsDisabled, setLogsDisabled] = React.useState(true);
React.useEffect(() => {
hasLogAccess().then(response => {
setLogsDisabled(!response);
});
}, []);
const { logsEnabled, logsStatusMessage } = useLogAccessInfo();
const linkStyle = React.useCallback((disabled) => {
return disabled ? {
background: themeV9.colorNeutralBackgroundDisabled,
Expand All @@ -326,9 +321,10 @@ export const SubNavigation: React.FunctionComponent<SubNavigationProps> = ({
<Stack horizontal>
<Stack.Item grow={0} className={navStyles.wrapper}>
{subMenuItems[mainNav]?.map((row, idx) => {
const linkDisabled = (row.itemKey === "/topology/logs" && logsDisabled) || (row.itemKey.indexOf("security") > -1 && !isAdmin);
const linkDisabled = (row.itemKey === "/topology/logs" && !logsEnabled) || (row.itemKey.indexOf("security") > -1 && !isAdmin);
return <Link
disabled={linkDisabled}
title={row.itemKey === "/topology/logs" && !logsEnabled ? logsStatusMessage : ""}
key={`MenuLink_${idx}`}
href={`#${row.itemKey}`}
className={[
Expand Down
17 changes: 5 additions & 12 deletions esp/src/src-react/components/WorkunitDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { WsWorkunits, WorkunitsService } from "@hpcc-js/comms";
import { scopedLogger } from "@hpcc-js/util";
import { SizeMe } from "react-sizeme";
import nlsHPCC from "src/nlsHPCC";
import { hasLogAccess } from "src/ESPLog";
import { wuidToDate, wuidToTime } from "src/Utility";
import { emptyFilter, formatQuery } from "src/ESPWorkunit";
import { useLogAccessInfo } from "../hooks/platform";
import { Variable, useWorkunit, useWorkunitVariables } from "../hooks/workunit";
import { useDeepEffect } from "../hooks/deepHooks";
import { DojoAdapter } from "../layouts/DojoAdapter";
import { FullscreenFrame, FullscreenStack } from "../layouts/Fullscreen";
import { parseQuery, pushUrl, updateFullscreen } from "../util/history";
Expand Down Expand Up @@ -58,7 +57,7 @@ export const WorkunitDetails: React.FunctionComponent<WorkunitDetailsProps> = ({
const [variables, , , refreshVariables] = useWorkunitVariables(wuid);
const [otTraceParent, setOtTraceParent] = React.useState("");
const [logCount, setLogCount] = React.useState<number | string>("*");
const [logsDisabled, setLogsDisabled] = React.useState(true);
const { logsEnabled, logsStatusMessage } = useLogAccessInfo();
const [_nextPrev, setNextPrev] = useNextPrev();

const query = React.useMemo(() => {
Expand Down Expand Up @@ -121,13 +120,6 @@ export const WorkunitDetails: React.FunctionComponent<WorkunitDetailsProps> = ({
};
}, [nextWuid, query, setNextPrev, wuid]);

useDeepEffect(() => {
hasLogAccess().then(response => {
setLogsDisabled(!response);
return response;
});
}, [wuid], [queryParams]);

const onTabSelect = React.useCallback((tab: TabInfo) => {
pushUrl(tab.__state ?? `${parentUrl}/${wuid}/${tab.id}`);
updateFullscreen(fullscreen);
Expand Down Expand Up @@ -174,15 +166,16 @@ export const WorkunitDetails: React.FunctionComponent<WorkunitDetailsProps> = ({
id: "logs",
label: nlsHPCC.Logs,
count: logCount,
disabled: logsDisabled
tooltipText: !logsEnabled ? (logsStatusMessage || nlsHPCC.LogsDisabled) : null,
disabled: !logsEnabled
}, {
id: "eclsummary",
label: nlsHPCC.ECL
}, {
id: "xml",
label: nlsHPCC.XML
}];
}, [logCount, logsDisabled, workunit?.ApplicationValueCount, workunit?.DebugValueCount, workunit?.GraphCount, workunit?.HelpersCount, workunit?.ResourceURLCount, workunit?.ResultCount, workunit?.SourceFileCount, workunit?.VariableCount, workunit?.WorkflowCount, wuid]);
}, [logCount, logsEnabled, logsStatusMessage, workunit?.ApplicationValueCount, workunit?.DebugValueCount, workunit?.GraphCount, workunit?.HelpersCount, workunit?.ResourceURLCount, workunit?.ResultCount, workunit?.SourceFileCount, workunit?.VariableCount, workunit?.WorkflowCount, wuid]);

return <FullscreenFrame fullscreen={fullscreen}>
<SizeMe monitorHeight>{({ size }) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { Overflow, OverflowItem, SelectTabData, SelectTabEvent, Tab, TabList } from "@fluentui/react-components";
import { Overflow, OverflowItem, SelectTabData, SelectTabEvent, Tab, TabList, Tooltip } from "@fluentui/react-components";
import { Count } from "./Count";
import { TabInfo } from "./TabInfo";
import { OverflowMenu } from "../OverflowMenu";
Expand Down Expand Up @@ -28,7 +28,9 @@ export const OverflowTabList: React.FunctionComponent<OverflowTabListProps> = ({
tab.__state = state;
}
return <OverflowItem key={tab.id} id={tab.id} priority={tab.id === selected ? 2 : 1}>
<Tab value={tab.id} icon={tab.icon} disabled={tab.disabled}>{tab.label}<Count value={tab.count} /></Tab>
<Tooltip content={tab.tooltipText || tab.label} relationship="label">
<Tab value={tab.id} icon={tab.icon} disabled={tab.disabled}>{tab.label}<Count value={tab.count} /></Tab>
</Tooltip>
</OverflowItem>;
}), tabsIndex];
}, [selected, state, tabs]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export interface TabInfo {
label: string;
count?: string | number;
disabled?: boolean;
tooltipText?: string;
__state?: any;
}
34 changes: 25 additions & 9 deletions esp/src/src-react/hooks/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Octokit } from "octokit";
import { useConst } from "@fluentui/react-hooks";
import { scopedLogger } from "@hpcc-js/util";
import { LogaccessService, Topology, WsLogaccess, WsTopology, WorkunitsServiceEx } from "@hpcc-js/comms";
import nlsHPCC from "src/nlsHPCC";
import { getBuildInfo, BuildInfo, fetchModernMode } from "src/Session";
import { cmake_build_type, containerized, ModernMode } from "src/BuildInfo";
import { sessionKeyValStore, userKeyValStore } from "src/KeyValStore";
Expand Down Expand Up @@ -209,19 +210,34 @@ export function useModernMode(): {
return { modernMode, setModernMode };
}

export function useLogAccessInfo(): {
managerType: string;
columns: WsLogaccess.Column[]
} {
const [managerType, setManagerType] = React.useState("");
const [columns, setColumns] = React.useState<WsLogaccess.Column[]>();
interface LogAccessInfo {
logsEnabled: boolean;
logsManagerType: string;
logsColumns: WsLogaccess.Column[];
logsStatusMessage: string;
}

export function useLogAccessInfo(): LogAccessInfo {
const [logsEnabled, setLogsEnabled] = React.useState(false);
const [logsManagerType, setLogsManagerType] = React.useState("");
const [logsColumns, setLogsColumns] = React.useState<WsLogaccess.Column[]>();
const [logsStatusMessage, setLogsStatusMessage] = React.useState("");

React.useEffect(() => {
service.GetLogAccessInfo({}).then(response => {
setManagerType(response.RemoteLogManagerType ?? "");
setColumns(response?.Columns?.Column);
if (response.hasOwnProperty("Exceptions")) {
setLogsStatusMessage(response["Exceptions"]?.Exception[0]?.Message ?? nlsHPCC.LogAccess_GenericException);
} else {
if (response.RemoteLogManagerType === null) {
setLogsStatusMessage(nlsHPCC.LogAccess_LoggingNotConfigured);
} else {
setLogsEnabled(true);
setLogsManagerType(response.RemoteLogManagerType);
setLogsColumns(response?.Columns?.Column);
}
}
});
}, []);

return { managerType, columns };
return { logsEnabled, logsManagerType, logsColumns, logsStatusMessage };
}
3 changes: 3 additions & 0 deletions esp/src/src/nls/hpcc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,8 @@ export = {
Location: "Location",
Lock: "Lock",
LogAccessType: "Log Access Type",
LogAccess_GenericException: "ws_logaccess::GetLogAccessInfo, an exception has occurred.",
LogAccess_LoggingNotConfigured: "A logging engine has not been configured.",
LogDirectory: "Log Directory",
LogEventType: "Log Event Type",
LogFile: "Log File",
Expand Down Expand Up @@ -529,6 +531,7 @@ export = {
Login: "Login",
Logout: "Log Out",
Logs: "Logs",
LogsDisabled: "Logs Disabled",
LogVisualization: "Log Visualization",
LogVisualizationUnconfigured: "Log Visualization is not configured, please check your configuration manager settings.",
log_analysis_1: "log_analysis_1*",
Expand Down
29 changes: 29 additions & 0 deletions system/jlib/jstring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2925,3 +2925,32 @@ const char * stristr (const char *haystack, const char *needle)

return nullptr;
}

/**
* For preventing command injection, sanitize the argument to be passed to the system command.
* - Quote the entire argument with single quotes to prevent interpretation of shell metacharacters.
* - Since a single-quoted string can't contain single quotes, even escaped, replace each single
* quote in the argument with the sequence '"'"' . That closes the single quoted string, appends
* a literal single quote, and reopens the single quoted string
*/
StringBuffer& sanitizeCommandArg(const char* arg, StringBuffer& sanitized)
{
#if defined(__linux__) || defined(__APPLE__)
if (!isEmptyString(arg))
{
size_t len = strlen(arg);
sanitized.append('\'');
for (size_t i = 0; i < len; i++)
{
if (arg[i] == '\'')
sanitized.append(R"('"'"')");
else
sanitized.append(arg[i]);
}
sanitized.append('\'');
}
#else
sanitized.append(arg);
#endif
return sanitized;
}
3 changes: 3 additions & 0 deletions system/jlib/jstring.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -642,4 +642,7 @@ extern jlib_decl void processOptionString(const char * options, optionCallback c
extern jlib_decl const char * stristr(const char *haystack, const char *needle);
extern jlib_decl void getSnakeCase(StringBuffer & out, const char * camelValue);

// For preventing command injection, sanitize the argument to be passed to the system command
extern jlib_decl StringBuffer& sanitizeCommandArg(const char* arg, StringBuffer& sanitized);

#endif

0 comments on commit be62432

Please sign in to comment.