From 551e4e43c162357063f6e255003a03d8aeefc91e Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Fri, 4 Jun 2021 08:37:50 +0100 Subject: [PATCH] feat(sas9-support): execute arbitrary code on SAS9 using SASjs runner --- src/SAS9ApiClient.ts | 72 ++++++++++++++++++++++++++++++----------- src/SASViyaApiClient.ts | 6 ++-- src/SASjs.ts | 16 +++++---- 3 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/SAS9ApiClient.ts b/src/SAS9ApiClient.ts index 473ddcf..b02bf9e 100644 --- a/src/SAS9ApiClient.ts +++ b/src/SAS9ApiClient.ts @@ -1,4 +1,6 @@ -import axios, { AxiosInstance } from 'axios' +import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time' +import * as NodeFormData from 'form-data' +import { Sas9RequestClient } from './request/Sas9RequestClient' import { isUrl } from './utils' /** @@ -6,11 +8,11 @@ import { isUrl } from './utils' * */ export class SAS9ApiClient { - private httpClient: AxiosInstance + private requestClient: Sas9RequestClient - constructor(private serverUrl: string) { + constructor(private serverUrl: string, private jobsPath: string) { if (serverUrl) isUrl(serverUrl) - this.httpClient = axios.create({ baseURL: this.serverUrl }) + this.requestClient = new Sas9RequestClient(serverUrl, false) } /** @@ -33,27 +35,59 @@ export class SAS9ApiClient { /** * Executes code on a SAS9 server. * @param linesOfCode - an array of code lines to execute. - * @param serverName - the server to execute the code on. - * @param repositoryName - the repository to execute the code in. + * @param userName - the user name to log into the current SAS server. + * @param password - the password to log into the current SAS server. */ public async executeScript( linesOfCode: string[], - serverName: string, - repositoryName: string + userName: string, + password: string ) { - const requestPayload = linesOfCode.join('\n') + await this.requestClient.login(userName, password, this.jobsPath) - const executeScriptResponse = await this.httpClient.put( - `/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`, - `command=${requestPayload}`, - { - headers: { - Accept: 'application/json' - }, - responseType: 'text' - } + const formData = generateFileUploadForm(linesOfCode.join('\n')) + + const codeInjectorPath = `/User Folders/${userName}/My Folder/sasjs/runner` + const contentType = + 'multipart/form-data; boundary=' + formData.getBoundary() + const contentLength = formData.getLengthSync() + + const headers = { + 'cache-control': 'no-cache', + Accept: '*/*', + 'Content-Type': contentType, + 'Content-Length': contentLength, + Connection: 'keep-alive' + } + const storedProcessUrl = `${this.jobsPath}/?${ + '_program=' + codeInjectorPath + '&_debug=log' + }` + const response = await this.requestClient.post( + storedProcessUrl, + formData, + undefined, + contentType, + headers ) - return executeScriptResponse.data + return response.result as string } } + +const generateFileUploadForm = (data: any): NodeFormData => { + const formData = new NodeFormData() + const fileName = `sasjs-execute-sas9-${getTimestamp()}.sas` + formData.append(fileName, data, { + filename: `${fileName}.csv`, + contentType: 'text/plain' + }) + + return formData +} + +const getTimestamp = () => { + return timestampToYYYYMMDDHHMMSS() + .replace(/:/g, '') + .replace(/\//g, '') + .replace(/ /g, '') +} diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index 72e9fa6..0e19dbb 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -719,13 +719,11 @@ export class SASViyaApiClient { let formData if (typeof FormData === 'undefined') { formData = new NodeFormData() - formData.append('grant_type', 'authorization_code') - formData.append('code', authCode) } else { formData = new FormData() - formData.append('grant_type', 'authorization_code') - formData.append('code', authCode) } + formData.append('grant_type', 'authorization_code') + formData.append('code', authCode) const authResponse = await this.requestClient .post( diff --git a/src/SASjs.ts b/src/SASjs.ts index 4d9c71c..c19f778 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -59,15 +59,15 @@ export default class SASjs { public async executeScriptSAS9( linesOfCode: string[], - serverName: string, - repositoryName: string + userName: string, + password: string ) { this.isMethodSupported('executeScriptSAS9', ServerType.Sas9) return await this.sas9ApiClient?.executeScript( linesOfCode, - serverName, - repositoryName + userName, + password ) } @@ -624,7 +624,7 @@ export default class SASjs { ) sasApiClient.debug = this.sasjsConfig.debug } else if (this.sasjsConfig.serverType === ServerType.Sas9) { - sasApiClient = new SAS9ApiClient(serverUrl) + sasApiClient = new SAS9ApiClient(serverUrl, this.jobsPath) } } else { let sasClientConfig: any = null @@ -813,7 +813,11 @@ export default class SASjs { if (this.sasjsConfig.serverType === ServerType.Sas9) { if (this.sas9ApiClient) this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl) - else this.sas9ApiClient = new SAS9ApiClient(this.sasjsConfig.serverUrl) + else + this.sas9ApiClient = new SAS9ApiClient( + this.sasjsConfig.serverUrl, + this.jobsPath + ) } this.fileUploader = new FileUploader(