UNPKG

@sap/cli-core

Version:

Command-Line Interface (CLI) Core Module

215 lines (214 loc) 8.08 kB
import fs from "fs-extra"; import { set } from "lodash-es"; import { EOL } from "os"; import { URL } from "url"; import { get as getConfig, set as setConfig } from "../../../config/index.js"; import { AuthenticationMethod, OPTION_OUTPUT, SEGMENTS_TO_REMOVE_FOR_PASSCODE_AUTH, X_CSRF_TOKEN, X_DSP_API_DEPRECATED_PROPERTIES, X_OUTPUT_FILE_NAME, } from "../../../constants.js"; import { get as getLogger } from "../../../logger/index.js"; import { ResultHandlerFactory } from "../../../result/ResultHandlerFactory.js"; import { getAuthenticationMethod, getTargetHost } from "../utils.js"; const CURLY_OPEN_ENCODED = encodeURIComponent("{"); const CURLY_CLOSED_ENCODED = encodeURIComponent("}"); export const checkConfiguration = (config) => { if (!config.authorization) { throw new Error("no authorization header"); } if (!config.host) { throw new Error("no host"); } if (!config.publicfqdn) { throw new Error("no publicfqdn"); } }; export const removeLeadingPathSegmentForPasscode = (path) => { const authenticationMethod = getAuthenticationMethod(); if (authenticationMethod === AuthenticationMethod.passcode) { const segments = path.split("/"); if (SEGMENTS_TO_REMOVE_FOR_PASSCODE_AUTH.includes(segments[1])) { segments.splice(1, 1); return segments.join("/"); } } return path; }; const getUrl = (path) => { if (path.startsWith("http")) { return new URL(path); } const pathInt = removeLeadingPathSegmentForPasscode(path); const targetHost = getTargetHost(); return new URL(`${targetHost}${pathInt}`); }; export const handleMappingIn = (mapping, url, value, headers, bodyWrapper) => { if (mapping.in === "query") { url.searchParams.append(mapping.name, value); } else if (mapping.in === "header") { // eslint-disable-next-line no-param-reassign headers[mapping.name] = value; } else if (mapping.in === "path") { // eslint-disable-next-line no-param-reassign url.pathname = url.pathname.replace(new RegExp(`${CURLY_OPEN_ENCODED}${mapping.name}${CURLY_CLOSED_ENCODED}`, "g"), encodeURIComponent(value)); } else { if (!bodyWrapper.body) { // eslint-disable-next-line no-param-reassign bodyWrapper.body = {}; } set(bodyWrapper.body, mapping.name, value); } }; export const getValueFromMappping = (mapping, config) => { if (mapping.source.type === "value") { return mapping.source.value; } if ( // mapping.source.type === "option" config.options[mapping.source.name] === undefined && mapping.source.dataType === "boolean") { return false; } // mapping.source.type === "option" return config.options[mapping.source.name]; }; export const handleParameterMapping = (config, url, headers, bodyWrapper) => (mapping) => { const value = getValueFromMappping(mapping, config); if (value === undefined || value === null) { return; } handleMappingIn(mapping, url, value, headers, bodyWrapper); }; export const buildParameters = (path, parameterMappings) => { const config = getConfig(); const bodyWrapper = { body: undefined, }; let headers = {}; if (config.data?.type === "file") { // stackoverflow.com/a/59177066 headers = config.data.content.getHeaders(); bodyWrapper.body = config.data.content; } else if (config.data?.type === "json") { headers = { "Content-Type": "application/json" }; bodyWrapper.body = config.data.content; } const url = getUrl(path); parameterMappings?.forEach(handleParameterMapping(config, url, headers, bodyWrapper)); return { url, headers, body: bodyWrapper.body, }; }; /** * If --output is present: Return value of --output if defined, otherwise take value from outputPath. * If --output is not present, print response to console * @param outputPath Optional path defined by server * @returns Path to output file */ export function getOutputFileName(outputPath = "") { const config = getConfig(); if (typeof config.options[OPTION_OUTPUT.longName] === "boolean" && config.options[OPTION_OUTPUT.longName]) { return outputPath; } return config.options[OPTION_OUTPUT.longName]; } export function checkResponseDataForInvalidLoginData(response) { if ( // jira.tools.sap/browse/DW101-73607 typeof response === "string" && response.includes("Please contact your system administrator and ensure you have an active account on this system.")) { const logger = getLogger("checkResponseDataForInvalidLoginData"); logger.output("WARNING: You are using an access token which is not known to the tenant. Please check your login credentials."); logger.debug(response); return true; } return false; } export const handleResponseData = async (data, outputPath) => { checkResponseDataForInvalidLoginData(data); const config = getConfig(); if (!config.doNotStoreResult) { ResultHandlerFactory.get().setResult(data); } if (config.doNotProcessResponseData) { return; } const { output } = getLogger("handler.fetch"); const formatted = config.options.pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data); const outputFileName = getOutputFileName(outputPath); if (outputFileName) { await fs.writeFile(outputFileName, formatted); } else { output(formatted); } }; export function handleResponseHeaders(headers) { const { error, output } = getLogger("handler.fetch.handleResponseHeaders"); if (headers[X_DSP_API_DEPRECATED_PROPERTIES]) { try { const deprecatedProperties = JSON.parse(headers[X_DSP_API_DEPRECATED_PROPERTIES]); const deprecatedPropertiesStr = deprecatedProperties .map((dp) => { let msg = dp.name; if (dp.deprecatedWithWave) { msg += `. Deprecated since version ${dp.deprecatedWithWave}`; } if (dp.decommissionedAfterWave) { msg += `. Decommissioned after version ${dp.decommissionedAfterWave}`; } if (dp.sapHelpUrl) { msg += `. See the SAP Help documentation at ${dp.sapHelpUrl} for more information`; } if (dp.customMessage) { msg += `. ${dp.customMessage}`; } return msg; }) .reduce((prev, curr) => `${prev}${EOL}\t${curr}`, ""); output(`WARNING: the following properties are deprecated:${deprecatedPropertiesStr}`); } catch (err) { error(`failed to parse deprecated properties via header ${X_DSP_API_DEPRECATED_PROPERTIES}`, err); } } } export const handleResponse = async (data, headers) => { if (headers?.[X_CSRF_TOKEN]) { setConfig({ [X_CSRF_TOKEN]: headers[X_CSRF_TOKEN] }); } else { if (data) { await handleResponseData(data, headers?.[X_OUTPUT_FILE_NAME]); } if (headers) { handleResponseHeaders(headers); } } }; export const configRequiresBody = (method) => ["POST", "PUT", "PATCH"].includes(method.toUpperCase()); export const buildHttpConfig = async (method, path, parameterMappings) => { const config = getConfig(); const { url, body, headers } = buildParameters(path, parameterMappings); const httpConfig = { url: url.toString(), method, data: configRequiresBody(method) ? body : undefined, headers: { ...config.authorization, ...headers, ...config.headers, publicfqdn: config.publicfqdn, }, }; if (getAuthenticationMethod() === AuthenticationMethod.oauth) { delete httpConfig.headers.publicfqdn; } return httpConfig; };