From 4c302971b6fed4f6770e04ac522dfa8cfda9850a Mon Sep 17 00:00:00 2001 From: 1000TurquoisePogs Date: Fri, 28 Aug 2026 15:48:17 -0400 Subject: [PATCH] Fix lazy put calls Signed-off-by: 1000TurquoisePogs --- WebContent/js/actions/editor.ts | 6 ++--- WebContent/js/actions/jobSubmitter.ts | 4 +-- WebContent/js/actions/treeDatasets.ts | 32 +++++++++++----------- WebContent/js/utilities/urlUtils.ts | 39 ++++++++++++++++++++++----- 4 files changed, 53 insertions(+), 28 deletions(-) diff --git a/WebContent/js/actions/editor.ts b/WebContent/js/actions/editor.ts index 914793bf..a59f0981 100644 --- a/WebContent/js/actions/editor.ts +++ b/WebContent/js/actions/editor.ts @@ -12,7 +12,7 @@ import { fetchDatasetTreeChildren } from './treeDS'; import { fetchDSMembers } from './treeDatasets'; import { - atlasGet, atlasPut, atlasPost, encodeURLComponent, + atlasGet, atlasPutText, atlasPost, encodeURLComponent, } from '../utilities/urlUtils'; import { constructAndPushMessage } from './snackbarNotifications'; import { checkForValidationFailure } from './validation'; @@ -190,7 +190,7 @@ export function saveDataset(file, content, etag) { return dispatch => { dispatch(requestSave(file)); const endpoint = `/restfiles/ds/${encodeURLComponent(file)}`; - return atlasPut(endpoint, content, etag) + return atlasPutText(endpoint, content, etag) .then(response => { return dispatch(checkForValidationFailure(response)); }) @@ -256,7 +256,7 @@ export function saveAsDatasetMember(DSName, newDSMember, newContent) { return dispatch => { const newDS = `${DSName}(${newDSMember})`; dispatch(requestSaveAs(newDS)); - return atlasPut(`/restfiles/ds/${encodeURIComponent(newDS)}`, newContent, null) + return atlasPutText(`/restfiles/ds/${encodeURIComponent(newDS)}`, newContent, null) .then(response => { return dispatch(checkForValidationFailure(response)); }) diff --git a/WebContent/js/actions/jobSubmitter.ts b/WebContent/js/actions/jobSubmitter.ts index 1e2b8d84..db94247d 100644 --- a/WebContent/js/actions/jobSubmitter.ts +++ b/WebContent/js/actions/jobSubmitter.ts @@ -10,7 +10,7 @@ */ import { constructAndPushMessage } from './snackbarNotifications'; -import { atlasPut } from '../utilities/urlUtils'; +import { atlasPutJson } from '../utilities/urlUtils'; import { checkForValidationFailure } from './validation'; export const REQUEST_JOB_SUBMIT = 'REQUEST_JOB_SUBMIT'; @@ -52,7 +52,7 @@ export function resetResponse() { export function submitJob(job) { return dispatch => { dispatch(requestSubmit()); - return atlasPut('/restjobs/jobs', JSON.stringify({ request: 'Submit Job', file: `//'${job}'` })) + return atlasPutJson('/restjobs/jobs', { request: 'Submit Job', file: `//'${job}'` }) .then(response => { return dispatch(checkForValidationFailure(response)); }) diff --git a/WebContent/js/actions/treeDatasets.ts b/WebContent/js/actions/treeDatasets.ts index 91df86f6..42fe8b65 100644 --- a/WebContent/js/actions/treeDatasets.ts +++ b/WebContent/js/actions/treeDatasets.ts @@ -13,7 +13,7 @@ import HTTPStatusCodes from '../constants/HTTPStatusCodeConstants'; import { fetchDatasetTreeChildren, removeDataset, renameDataset as renameDatasetRefresh } from './treeDS'; import { invalidateContent, updateEditorFileName } from './editor'; import { - atlasGet, atlasPost, atlasPut, atlasDelete, + atlasGet, atlasPost, atlasPutText, atlasPutJson, atlasDelete, } from '../utilities/urlUtils'; import { constructAndPushMessage } from './snackbarNotifications'; import { checkForValidationFailure } from './validation'; @@ -231,7 +231,7 @@ export function fetchDSMembers(DSName: string) { export function createMember(DSName: string, member: string) { return dispatch => { dispatch(requestNewMember(DSName, member)); - return atlasPut(`/restfiles/ds/${encodeURIComponent(DSName)}(${encodeURIComponent(member)})`, '') + return atlasPutText(`/restfiles/ds/${encodeURIComponent(DSName)}(${encodeURIComponent(member)})`, '') .then(response => { return dispatch(checkForValidationFailure(response)); }) @@ -326,22 +326,22 @@ export function renameDataset(oldName: string, newName: string, isOpenInViewer: dispatch(requestRenameDataset(oldName)); /** Check if we are renaming the Dataset or Dataser Member and create the renameBody accordinly */ if (oldName.indexOf(')') === oldName.length - 1) { - renameBody = `{ - "request": "rename", - "from-dataset": { - "dsn": "${oldName.substring(0, oldName.indexOf('('))}", - "member": "${oldName.substring(oldName.lastIndexOf('(') + 1, oldName.length - 1)}" - } - }`; + renameBody = { + request: 'rename', + 'from-dataset': { + dsn: oldName.substring(0, oldName.indexOf('(')), + member: oldName.substring(oldName.lastIndexOf('(') + 1, oldName.length - 1), + }, + }; } else { - renameBody = `{ - "request": "rename", - "from-dataset": { - "dsn": "${oldName}" - } - }`; + renameBody = { + request: 'rename', + 'from-dataset': { + dsn: oldName, + }, + }; } - return atlasPut(`/restfiles/ds/${encodeURIComponent(newName)}`, renameBody) + return atlasPutJson(`/restfiles/ds/${encodeURIComponent(newName)}`, renameBody) .then(response => { return dispatch(checkForValidationFailure(response)); }) diff --git a/WebContent/js/utilities/urlUtils.ts b/WebContent/js/utilities/urlUtils.ts index 7dfb6200..3560d637 100644 --- a/WebContent/js/utilities/urlUtils.ts +++ b/WebContent/js/utilities/urlUtils.ts @@ -59,13 +59,10 @@ export function atlasPost(endpoint: string, body) { return atlasAction(endpoint, fetchParams); } -export function atlasPut(endpoint: string, body: string, etag) { - let header; - if (body.includes('"request": "rename"') || body.includes('"request":"Submit Job"')) { - header = { 'Content-Type': 'application/json', 'X-CSRF-ZOSMF-HEADER': '*' }; - } else { - header = { 'Content-Type': 'text/plain', 'X-IBM-Data-Type': 'text', 'X-CSRF-ZOSMF-HEADER': '*' }; - } +// Raw dataset/member content write. Content-Type is fixed, never inferred from body, so a +// malicious buffer can't be relabelled as a z/OSMF utility request (see atlasPutJson). +export function atlasPutText(endpoint: string, body: string, etag) { + const header = { 'Content-Type': 'text/plain', 'X-IBM-Data-Type': 'text', 'X-CSRF-ZOSMF-HEADER': '*' }; if (etag) { header['If-Match'] = etag; } @@ -77,3 +74,31 @@ export function atlasPut(endpoint: string, body: string, etag) { }; return atlasAction(endpoint, fetchParams); } + +// z/OSMF "rename" utility request, see restfiles data set REST interface docs. +interface RenameDatasetRequest { + request: 'rename'; + 'from-dataset': { + dsn: string; + member?: string; + }; +} + +// z/OSMF "Submit Job" utility request, see restjobs REST interface docs. +interface SubmitJobRequest { + request: 'Submit Job'; + file: string; +} + +type AtlasJsonRequestBody = RenameDatasetRequest | SubmitJobRequest; + +// z/OSMF utility request (rename, submit job); body shape is restricted to known request types, never raw editor content. +export function atlasPutJson(endpoint: string, body: AtlasJsonRequestBody) { + const fetchParams = { + method: 'PUT', + body: JSON.stringify(body), + headers: { 'Content-Type': 'application/json', 'X-CSRF-ZOSMF-HEADER': '*' }, + credentials: 'include', + }; + return atlasAction(endpoint, fetchParams); +}