mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 09:24:35 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b645d1495b | ||
| 3a4a4c3460 | |||
|
|
182de51f9b | ||
|
|
712d1549c7 | ||
|
|
5a478c8936 | ||
| c9ecc1dde4 | |||
| bdf9e2fd5b | |||
|
|
0b795b26c0 | ||
| 96aac0cfa2 | |||
|
|
a82e1f33e3 | ||
|
|
058c887cd3 | ||
|
|
81d959c7c1 | ||
| 7c5adeabb5 | |||
|
|
cb88376bda | ||
|
|
4c8ddeca25 | ||
|
|
d264a3f239 | ||
|
|
840b1aa1bf | ||
|
|
e26fd307c8 | ||
|
|
62deaf9f03 | ||
|
|
865bf71f7d | ||
|
|
734c5bccaa | ||
|
|
bc82cb5f5e | ||
|
|
f25c76fdfd | ||
|
|
e649b41e9e | ||
|
|
a962979765 | ||
| 9dc0499f66 | |||
|
|
bc1a7dc54f | ||
|
|
93c267fd4e | ||
|
|
de5c38f0fb | ||
|
|
208470e7d9 | ||
|
|
ff4915f7f3 | ||
|
|
6ff8eece7b | ||
|
|
2849e6ed07 | ||
|
|
7f590c35da | ||
|
|
e975e7de97 | ||
|
|
f0ecfa57e5 |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -13,14 +13,15 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [15.x]
|
||||
node-version: [lts/fermium]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
- name: Check code style
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
225
docs/classes/api_viya_spec.mockstream.html
Normal file
225
docs/classes/api_viya_spec.mockstream.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
358
docs/classes/job_execution.fileuploader.html
Normal file
358
docs/classes/job_execution.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
363
docs/classes/job_execution.sas9jobexecutor.html
Normal file
363
docs/classes/job_execution.sas9jobexecutor.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1102
docs/classes/request.sas9requestclient.html
Normal file
1102
docs/classes/request.sas9requestclient.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
282
docs/classes/root.sasjsapiclient.html
Normal file
282
docs/classes/root.sasjsapiclient.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
281
docs/classes/types_errors.invalidjsonerror.html
Normal file
281
docs/classes/types_errors.invalidjsonerror.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
308
docs/classes/types_errors.jobstatepollerror.html
Normal file
308
docs/classes/types_errors.jobstatepollerror.html
Normal file
File diff suppressed because one or more lines are too long
281
docs/classes/types_errors.jsonparsearrayerror.html
Normal file
281
docs/classes/types_errors.jsonparsearrayerror.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
347
docs/classes/types_errors.nosessionstateerror.html
Normal file
347
docs/classes/types_errors.nosessionstateerror.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
293
docs/classes/types_errors.rootfoldernotfounderror.html
Normal file
293
docs/classes/types_errors.rootfoldernotfounderror.html
Normal file
File diff suppressed because one or more lines are too long
281
docs/classes/types_errors.sas9autherror.html
Normal file
281
docs/classes/types_errors.sas9autherror.html
Normal file
File diff suppressed because one or more lines are too long
305
docs/classes/types_errors.weboutresponseerror.html
Normal file
305
docs/classes/types_errors.weboutresponseerror.html
Normal file
File diff suppressed because one or more lines are too long
235
docs/enums/types.loginmechanism.html
Normal file
235
docs/enums/types.loginmechanism.html
Normal file
File diff suppressed because one or more lines are too long
235
docs/enums/types.membertype.html
Normal file
235
docs/enums/types.membertype.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
139
docs/index.html
139
docs/index.html
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
243
docs/interfaces/types.executionquery.html
Normal file
243
docs/interfaces/types.executionquery.html
Normal file
File diff suppressed because one or more lines are too long
279
docs/interfaces/types.file.html
Normal file
279
docs/interfaces/types.file.html
Normal file
File diff suppressed because one or more lines are too long
225
docs/interfaces/types.filetree.html
Normal file
225
docs/interfaces/types.filetree.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
261
docs/interfaces/types.foldermember.html
Normal file
261
docs/interfaces/types.foldermember.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
225
docs/interfaces/types.loginoptions.html
Normal file
225
docs/interfaces/types.loginoptions.html
Normal file
File diff suppressed because one or more lines are too long
243
docs/interfaces/types.loginresult.html
Normal file
243
docs/interfaces/types.loginresult.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
147
docs/interfaces/types.process.html
Normal file
147
docs/interfaces/types.process.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
261
docs/interfaces/types.servicemember.html
Normal file
261
docs/interfaces/types.servicemember.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
285
docs/interfaces/types.writestream.html
Normal file
285
docs/interfaces/types.writestream.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
455
docs/modules/api_viya.html
Normal file
455
docs/modules/api_viya.html
Normal file
File diff suppressed because one or more lines are too long
452
docs/modules/api_viya_spec.html
Normal file
452
docs/modules/api_viya_spec.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
251
docs/modules/test.html
Normal file
251
docs/modules/test.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
21057
package-lock.json
generated
21057
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@@ -41,30 +41,34 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/form-data": "^2.5.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/mime": "^2.0.3",
|
||||
"@types/pem": "^1.9.6",
|
||||
"@types/tough-cookie": "^4.0.1",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cp": "^0.2.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1",
|
||||
"jest": "^27.2.0",
|
||||
"jest-extended": "^0.11.5",
|
||||
"node-polyfill-webpack-plugin": "^1.1.4",
|
||||
"path": "^0.12.7",
|
||||
"pem": "^1.14.4",
|
||||
"process": "^0.11.10",
|
||||
"rimraf": "^3.0.2",
|
||||
"semantic-release": "^17.4.7",
|
||||
"semantic-release": "^18.0.0",
|
||||
"terser-webpack-plugin": "^5.2.4",
|
||||
"ts-jest": "^27.0.3",
|
||||
"ts-loader": "^9.2.2",
|
||||
"ts-loader": "^9.2.6",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.22.3",
|
||||
"typedoc": "0.19.2",
|
||||
"typedoc-neo-theme": "^1.1.1",
|
||||
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||
"typescript": "4.3.5",
|
||||
"webpack": "^5.52.1",
|
||||
"webpack": "^5.56.0",
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
"main": "index.js",
|
||||
|
||||
@@ -13,7 +13,6 @@ const defaultConfig: SASjsConfig = {
|
||||
debug: false,
|
||||
contextName: 'SAS Job Execution compute context',
|
||||
useComputeApi: false,
|
||||
allowInsecureRequests: false,
|
||||
loginMechanism: LoginMechanism.Default
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as https from 'https'
|
||||
import { generateTimestamp } from '@sasjs/utils/time'
|
||||
import * as NodeFormData from 'form-data'
|
||||
import { Sas9RequestClient } from './request/Sas9RequestClient'
|
||||
@@ -13,10 +14,10 @@ export class SAS9ApiClient {
|
||||
constructor(
|
||||
private serverUrl: string,
|
||||
private jobsPath: string,
|
||||
allowInsecureRequests: boolean
|
||||
httpsAgentOptions?: https.AgentOptions
|
||||
) {
|
||||
if (serverUrl) isUrl(serverUrl)
|
||||
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
|
||||
this.requestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
29
src/SASjs.ts
29
src/SASjs.ts
@@ -33,6 +33,7 @@ import { LoginOptions, LoginResult } from './types/Login'
|
||||
|
||||
const defaultConfig: SASjsConfig = {
|
||||
serverUrl: '',
|
||||
pathSASJS: '/SASjsApi/stp/execute',
|
||||
pathSAS9: '/SASStoredProcess/do',
|
||||
pathSASViya: '/SASJobExecution',
|
||||
appLoc: '/Public/seedapp',
|
||||
@@ -40,7 +41,6 @@ const defaultConfig: SASjsConfig = {
|
||||
debug: false,
|
||||
contextName: 'SAS Job Execution compute context',
|
||||
useComputeApi: null,
|
||||
allowInsecureRequests: false,
|
||||
loginMechanism: LoginMechanism.Default
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class SASjs {
|
||||
private jesJobExecutor: JobExecutor | null = null
|
||||
private sas9JobExecutor: JobExecutor | null = null
|
||||
|
||||
constructor(config?: any) {
|
||||
constructor(config?: Partial<SASjsConfig>) {
|
||||
this.sasjsConfig = {
|
||||
...defaultConfig,
|
||||
...config
|
||||
@@ -528,10 +528,11 @@ export default class SASjs {
|
||||
|
||||
/**
|
||||
* Checks whether a session is active, or login is required.
|
||||
* @param accessToken - an optional access token is required for SASjs server type.
|
||||
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
||||
*/
|
||||
public async checkSession() {
|
||||
return this.authManager!.checkSession()
|
||||
public async checkSession(accessToken?: string) {
|
||||
return this.authManager!.checkSession(accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -564,9 +565,10 @@ export default class SASjs {
|
||||
|
||||
/**
|
||||
* Logs out of the configured SAS server.
|
||||
* @param accessToken - an optional access token is required for SASjs server type.
|
||||
*/
|
||||
public logOut() {
|
||||
return this.authManager!.logOut()
|
||||
public logOut(accessToken?: string) {
|
||||
return this.authManager!.logOut(accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -797,7 +799,7 @@ export default class SASjs {
|
||||
sasApiClient = new SAS9ApiClient(
|
||||
serverUrl,
|
||||
this.jobsPath,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
this.sasjsConfig.httpsAgentOptions
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@@ -964,19 +966,21 @@ export default class SASjs {
|
||||
if (!this.requestClient) {
|
||||
this.requestClient = new RequestClient(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
this.sasjsConfig.httpsAgentOptions
|
||||
)
|
||||
} else {
|
||||
this.requestClient.setConfig(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
this.sasjsConfig.httpsAgentOptions
|
||||
)
|
||||
}
|
||||
|
||||
this.jobsPath =
|
||||
this.sasjsConfig.serverType === ServerType.SasViya
|
||||
? this.sasjsConfig.pathSASViya
|
||||
: this.sasjsConfig.pathSAS9
|
||||
: this.sasjsConfig.serverType === ServerType.Sas9
|
||||
? this.sasjsConfig.pathSAS9
|
||||
: this.sasjsConfig.pathSASJS || ''
|
||||
|
||||
this.authManager = new AuthManager(
|
||||
this.sasjsConfig.serverUrl,
|
||||
@@ -1010,7 +1014,7 @@ export default class SASjs {
|
||||
this.sas9ApiClient = new SAS9ApiClient(
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.jobsPath,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
this.sasjsConfig.httpsAgentOptions
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1045,7 +1049,8 @@ export default class SASjs {
|
||||
this.sasjsConfig.serverUrl,
|
||||
this.sasjsConfig.serverType!,
|
||||
this.jobsPath,
|
||||
this.sasjsConfig.allowInsecureRequests
|
||||
this.requestClient,
|
||||
this.sasjsConfig.httpsAgentOptions
|
||||
)
|
||||
|
||||
this.computeJobExecutor = new ComputeJobExecutor(
|
||||
|
||||
@@ -21,7 +21,9 @@ export class AuthManager {
|
||||
this.logoutUrl =
|
||||
this.serverType === ServerType.Sas9
|
||||
? '/SASLogon/logout?'
|
||||
: '/SASLogon/logout.do?'
|
||||
: this.serverType === ServerType.SasViya
|
||||
? '/SASLogon/logout.do?'
|
||||
: '/SASjsApi/auth/logout'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,20 +182,21 @@ export class AuthManager {
|
||||
|
||||
/**
|
||||
* Checks whether a session is active, or login is required.
|
||||
* @param accessToken - an optional access token is required for SASjs server type.
|
||||
* @returns - a promise which resolves with an object containing three values
|
||||
* - a boolean `isLoggedIn`
|
||||
* - a string `userName` and
|
||||
* - a form `loginForm` if not loggedin.
|
||||
*/
|
||||
public async checkSession(): Promise<{
|
||||
public async checkSession(accessToken?: string): Promise<{
|
||||
isLoggedIn: boolean
|
||||
userName: string
|
||||
loginForm?: any
|
||||
}> {
|
||||
const { isLoggedIn, userName } = await this.fetchUserName()
|
||||
const { isLoggedIn, userName } = await this.fetchUserName(accessToken)
|
||||
let loginForm = null
|
||||
|
||||
if (!isLoggedIn) {
|
||||
if (!isLoggedIn && this.serverType !== ServerType.Sasjs) {
|
||||
//We will logout to make sure cookies are removed and login form is presented
|
||||
//Residue can happen in case of session expiration
|
||||
await this.logOut()
|
||||
@@ -218,19 +221,20 @@ export class AuthManager {
|
||||
return await this.getLoginForm(formResponse)
|
||||
}
|
||||
|
||||
private async fetchUserName(): Promise<{
|
||||
private async fetchUserName(accessToken?: string): Promise<{
|
||||
isLoggedIn: boolean
|
||||
userName: string
|
||||
}> {
|
||||
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
|
||||
//For SAS9 we will send request on SASStoredProcess
|
||||
const url =
|
||||
this.serverType === ServerType.SasViya
|
||||
? `${this.serverUrl}/identities/users/@currentUser`
|
||||
: `${this.serverUrl}/SASStoredProcess`
|
||||
: this.serverType === ServerType.Sas9
|
||||
? `${this.serverUrl}/SASStoredProcess`
|
||||
: `${this.serverUrl}/SASjsApi/session`
|
||||
|
||||
// Access token is required for server type `SASjs`
|
||||
const { result: loginResponse } = await this.requestClient
|
||||
.get<string>(url, undefined, 'text/plain')
|
||||
.get<string>(url, accessToken, 'text/plain')
|
||||
.catch((err: any) => {
|
||||
return { result: 'authErr' }
|
||||
})
|
||||
@@ -256,7 +260,12 @@ export class AuthManager {
|
||||
.split(' ')
|
||||
.map((name: string) => name.slice(0, 3).toLowerCase())
|
||||
.join('')
|
||||
|
||||
case ServerType.Sasjs:
|
||||
return response?.username
|
||||
|
||||
default:
|
||||
console.error('Server Type not found in extractUserName function')
|
||||
return ''
|
||||
}
|
||||
}
|
||||
@@ -304,8 +313,12 @@ export class AuthManager {
|
||||
|
||||
/**
|
||||
* Logs out of the configured SAS server.
|
||||
* @param accessToken - an optional access token is required for SASjs server type.
|
||||
*/
|
||||
public logOut() {
|
||||
public logOut(accessToken?: string) {
|
||||
if (this.serverType === ServerType.Sasjs) {
|
||||
return this.requestClient.post(this.logoutUrl, undefined, accessToken)
|
||||
}
|
||||
this.requestClient.clearCsrfTokens()
|
||||
return this.requestClient.get(this.logoutUrl, undefined).then(() => true)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function getAccessToken(
|
||||
)
|
||||
.then((res) => res.result as SasAuthResponse)
|
||||
.catch((err) => {
|
||||
throw prefixMessage(err, 'Error while getting access token')
|
||||
throw prefixMessage(err, 'Error while getting access token. ')
|
||||
})
|
||||
|
||||
return authResponse
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import * as https from 'https'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
import * as NodeFormData from 'form-data'
|
||||
import { ErrorResponse } from '../types/errors'
|
||||
import { convertToCSV, isRelativePath } from '../utils'
|
||||
import { BaseJobExecutor } from './JobExecutor'
|
||||
import { Sas9RequestClient } from '../request/Sas9RequestClient'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
|
||||
/**
|
||||
* Job executor for SAS9 servers for use in Node.js environments.
|
||||
@@ -12,15 +14,16 @@ import { Sas9RequestClient } from '../request/Sas9RequestClient'
|
||||
* job execution requests.
|
||||
*/
|
||||
export class Sas9JobExecutor extends BaseJobExecutor {
|
||||
private requestClient: Sas9RequestClient
|
||||
private sas9RequestClient: Sas9RequestClient
|
||||
constructor(
|
||||
serverUrl: string,
|
||||
serverType: ServerType,
|
||||
private jobsPath: string,
|
||||
allowInsecureRequests: boolean
|
||||
private requestClient: RequestClient,
|
||||
httpsAgentOptions?: https.AgentOptions
|
||||
) {
|
||||
super(serverUrl, serverType)
|
||||
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
|
||||
this.sas9RequestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
|
||||
}
|
||||
|
||||
async execute(sasJob: string, data: any, config: any) {
|
||||
@@ -36,6 +39,8 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
||||
: ''
|
||||
}`
|
||||
|
||||
apiUrl = `${apiUrl}${config.debug ? '&_debug=131' : ''}`
|
||||
|
||||
let requestParams = {
|
||||
...this.getRequestParams(config)
|
||||
}
|
||||
@@ -48,6 +53,8 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
||||
} catch (e: any) {
|
||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||
}
|
||||
} else {
|
||||
data = ''
|
||||
}
|
||||
|
||||
for (const key in requestParams) {
|
||||
@@ -56,16 +63,18 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
await this.requestClient.login(
|
||||
await this.sas9RequestClient.login(
|
||||
config.username,
|
||||
config.password,
|
||||
this.jobsPath
|
||||
)
|
||||
|
||||
const contentType =
|
||||
data && Object.keys(data).length
|
||||
? 'multipart/form-data; boundary=' + (formData as any)._boundary
|
||||
: 'text/plain'
|
||||
return await this.requestClient!.post(
|
||||
|
||||
return await this.sas9RequestClient!.post(
|
||||
apiUrl,
|
||||
formData,
|
||||
undefined,
|
||||
@@ -75,6 +84,28 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
||||
Connection: 'Keep-Alive'
|
||||
}
|
||||
)
|
||||
.then((res: any) => {
|
||||
let resString = res
|
||||
|
||||
if (typeof res === 'object') {
|
||||
resString = JSON.stringify(res)
|
||||
}
|
||||
|
||||
this.requestClient!.appendRequest(resString, sasJob, config.debug)
|
||||
|
||||
return res
|
||||
})
|
||||
.catch((err: any) => {
|
||||
let errString = err
|
||||
|
||||
if (typeof err === 'object') {
|
||||
errString = JSON.stringify(errString)
|
||||
}
|
||||
|
||||
this.requestClient!.appendRequest(errString, sasJob, config.debug)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
private getRequestParams(config: any): any {
|
||||
|
||||
@@ -15,7 +15,8 @@ import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||
import {
|
||||
isRelativePath,
|
||||
parseSasViyaDebugResponse,
|
||||
appendExtraResponseAttributes
|
||||
appendExtraResponseAttributes,
|
||||
getValidJson
|
||||
} from '../utils'
|
||||
import { BaseJobExecutor } from './JobExecutor'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
@@ -113,6 +114,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
const stringifiedData = JSON.stringify(data)
|
||||
if (
|
||||
config.serverType === ServerType.Sas9 ||
|
||||
config.serverType === ServerType.Sasjs ||
|
||||
stringifiedData.length > 500000 ||
|
||||
stringifiedData.includes(';')
|
||||
) {
|
||||
@@ -142,12 +144,25 @@ export class WebJobExecutor extends BaseJobExecutor {
|
||||
}
|
||||
|
||||
const requestPromise = new Promise((resolve, reject) => {
|
||||
this.requestClient!.post(apiUrl, formData, undefined)
|
||||
// Access token is required for server type `SASjs`
|
||||
this.requestClient!.post(apiUrl, formData, authConfig?.access_token)
|
||||
.then(async (res: any) => {
|
||||
this.requestClient!.appendRequest(res, sasJob, config.debug)
|
||||
const resObj =
|
||||
this.serverType === ServerType.Sasjs
|
||||
? {
|
||||
result: res.result._webout,
|
||||
log: res.result.log
|
||||
}
|
||||
: res
|
||||
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
|
||||
|
||||
let jsonResponse = res.result
|
||||
|
||||
if (this.serverType === ServerType.Sasjs) {
|
||||
const webout = parseWeboutResponse(res.result._webout, apiUrl)
|
||||
jsonResponse = getValidJson(webout)
|
||||
}
|
||||
|
||||
if (config.debug) {
|
||||
switch (this.serverType) {
|
||||
case ServerType.SasViya:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import * as https from 'https'
|
||||
import { CsrfToken } from '..'
|
||||
import { isAuthorizeFormRequired, isLogInRequired } from '../auth'
|
||||
import {
|
||||
@@ -12,7 +13,11 @@ import { SASjsRequest } from '../types'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
import { SAS9AuthError } from '../types/errors/SAS9AuthError'
|
||||
import { parseGeneratedCode, parseSourceCode } from '../utils'
|
||||
import {
|
||||
parseGeneratedCode,
|
||||
parseSourceCode,
|
||||
createAxiosInstance
|
||||
} from '../utils'
|
||||
|
||||
export interface HttpClient {
|
||||
get<T>(
|
||||
@@ -54,12 +59,15 @@ export class RequestClient implements HttpClient {
|
||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||
protected httpClient!: AxiosInstance
|
||||
|
||||
constructor(protected baseUrl: string, allowInsecure = false) {
|
||||
this.createHttpClient(baseUrl, allowInsecure)
|
||||
constructor(
|
||||
protected baseUrl: string,
|
||||
httpsAgentOptions?: https.AgentOptions
|
||||
) {
|
||||
this.createHttpClient(baseUrl, httpsAgentOptions)
|
||||
}
|
||||
|
||||
public setConfig(baseUrl: string, allowInsecure = false) {
|
||||
this.createHttpClient(baseUrl, allowInsecure)
|
||||
public setConfig(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
|
||||
this.createHttpClient(baseUrl, httpsAgentOptions)
|
||||
}
|
||||
|
||||
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
||||
@@ -511,20 +519,15 @@ export class RequestClient implements HttpClient {
|
||||
return responseToReturn
|
||||
}
|
||||
|
||||
private createHttpClient(baseUrl: string, allowInsecure = false) {
|
||||
const https = require('https')
|
||||
if (allowInsecure && https.Agent) {
|
||||
this.httpClient = axios.create({
|
||||
baseURL: baseUrl,
|
||||
httpsAgent: new https.Agent({
|
||||
rejectUnauthorized: !allowInsecure
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.httpClient = axios.create({
|
||||
baseURL: baseUrl
|
||||
})
|
||||
}
|
||||
private createHttpClient(
|
||||
baseUrl: string,
|
||||
httpsAgentOptions?: https.AgentOptions
|
||||
) {
|
||||
const httpsAgent = httpsAgentOptions
|
||||
? new https.Agent(httpsAgentOptions)
|
||||
: undefined
|
||||
|
||||
this.httpClient = createAxiosInstance(baseUrl, httpsAgent)
|
||||
|
||||
this.httpClient.defaults.validateStatus = (status) =>
|
||||
status >= 200 && status < 401
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as https from 'https'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
import axiosCookieJarSupport from 'axios-cookiejar-support'
|
||||
import * as tough from 'tough-cookie'
|
||||
@@ -9,8 +10,8 @@ import { RequestClient, throwIfError } from './RequestClient'
|
||||
* Handles redirects and cookie management.
|
||||
*/
|
||||
export class Sas9RequestClient extends RequestClient {
|
||||
constructor(baseUrl: string, allowInsecure = false) {
|
||||
super(baseUrl, allowInsecure)
|
||||
constructor(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
|
||||
super(baseUrl, httpsAgentOptions)
|
||||
this.httpClient.defaults.maxRedirects = 0
|
||||
this.httpClient.defaults.validateStatus = (status) =>
|
||||
status >= 200 && status < 303
|
||||
|
||||
167
src/test/RequestClient.spec.ts
Normal file
167
src/test/RequestClient.spec.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import * as pem from 'pem'
|
||||
import * as http from 'http'
|
||||
import * as https from 'https'
|
||||
import { app, mockedAuthResponse } from './SAS_server_app'
|
||||
import { ServerType } from '@sasjs/utils'
|
||||
import SASjs from '../SASjs'
|
||||
import * as axiosModules from '../utils/createAxiosInstance'
|
||||
|
||||
const axiosActual = jest.requireActual('axios')
|
||||
|
||||
jest
|
||||
.spyOn(axiosModules, 'createAxiosInstance')
|
||||
.mockImplementation((baseURL: string, httpsAgent?: https.Agent) =>
|
||||
axiosActual.create({ baseURL, httpsAgent })
|
||||
)
|
||||
|
||||
const PORT = 8000
|
||||
const SERVER_URL = `https://localhost:${PORT}/`
|
||||
|
||||
const ERROR_MESSAGES = {
|
||||
selfSigned: 'self signed certificate',
|
||||
CCA: 'unable to verify the first certificate'
|
||||
}
|
||||
|
||||
describe('RequestClient', () => {
|
||||
let server: http.Server
|
||||
|
||||
const adapter = new SASjs({
|
||||
serverUrl: `http://localhost:${PORT}/`,
|
||||
serverType: ServerType.SasViya
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
await new Promise((resolve: any, reject: any) => {
|
||||
server = app
|
||||
.listen(PORT, () => resolve())
|
||||
.on('error', (err: any) => reject(err))
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
it('should response the POST method', async () => {
|
||||
const authResponse = await adapter.getAccessToken(
|
||||
'clientId',
|
||||
'clientSecret',
|
||||
'authCode'
|
||||
)
|
||||
|
||||
expect(authResponse.access_token).toBe(mockedAuthResponse.access_token)
|
||||
})
|
||||
|
||||
it('should response the POST method with Unauthorized', async () => {
|
||||
await expect(
|
||||
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
|
||||
).rejects.toThrow(
|
||||
'Error while getting access token. Request failed with status code 401'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('RequestClient - Self Signed Server', () => {
|
||||
let adapter: SASjs
|
||||
|
||||
let httpsServer: https.Server
|
||||
let sslConfig: pem.CertificateCreationResult
|
||||
|
||||
beforeAll(async () => {
|
||||
;({ httpsServer, keys: sslConfig } = await setupSelfSignedServer())
|
||||
await new Promise((resolve: any, reject: any) => {
|
||||
httpsServer
|
||||
.listen(PORT, () => resolve())
|
||||
.on('error', (err: any) => reject(err))
|
||||
})
|
||||
|
||||
adapter = new SASjs({
|
||||
serverUrl: SERVER_URL,
|
||||
serverType: ServerType.SasViya,
|
||||
httpsAgentOptions: { ca: [sslConfig.certificate] }
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
httpsServer.close()
|
||||
})
|
||||
|
||||
it('should throw error for not providing certificate', async () => {
|
||||
const adapterWithoutCertificate = new SASjs({
|
||||
serverUrl: SERVER_URL,
|
||||
serverType: ServerType.SasViya
|
||||
})
|
||||
|
||||
await expect(
|
||||
adapterWithoutCertificate.getAccessToken(
|
||||
'clientId',
|
||||
'clientSecret',
|
||||
'authCode'
|
||||
)
|
||||
).rejects.toThrow(
|
||||
`Error while getting access token. ${ERROR_MESSAGES.selfSigned}`
|
||||
)
|
||||
})
|
||||
|
||||
it('should response the POST method using insecure flag', async () => {
|
||||
const adapterAllowInsecure = new SASjs({
|
||||
serverUrl: SERVER_URL,
|
||||
serverType: ServerType.SasViya,
|
||||
httpsAgentOptions: { rejectUnauthorized: false }
|
||||
})
|
||||
|
||||
const authResponse = await adapterAllowInsecure.getAccessToken(
|
||||
'clientId',
|
||||
'clientSecret',
|
||||
'authCode'
|
||||
)
|
||||
|
||||
expect(authResponse.access_token).toBe(mockedAuthResponse.access_token)
|
||||
})
|
||||
|
||||
it('should response the POST method', async () => {
|
||||
const authResponse = await adapter.getAccessToken(
|
||||
'clientId',
|
||||
'clientSecret',
|
||||
'authCode'
|
||||
)
|
||||
|
||||
expect(authResponse.access_token).toBe(mockedAuthResponse.access_token)
|
||||
})
|
||||
|
||||
it('should response the POST method with Unauthorized', async () => {
|
||||
await expect(
|
||||
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
|
||||
).rejects.toThrow(
|
||||
'Error while getting access token. Request failed with status code 401'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
const setupSelfSignedServer = async (): Promise<{
|
||||
httpsServer: https.Server
|
||||
keys: pem.CertificateCreationResult
|
||||
}> => {
|
||||
return await new Promise(async (resolve) => {
|
||||
const keys = await createCertificate()
|
||||
|
||||
const httpsServer = https.createServer(
|
||||
{ key: keys.clientKey, cert: keys.certificate },
|
||||
app
|
||||
)
|
||||
|
||||
resolve({ httpsServer, keys })
|
||||
})
|
||||
}
|
||||
|
||||
const createCertificate = async (): Promise<pem.CertificateCreationResult> => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
pem.createCertificate(
|
||||
{ days: 1, selfSigned: true },
|
||||
(error: any, keys: pem.CertificateCreationResult) => {
|
||||
if (error) reject(false)
|
||||
resolve(keys)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
38
src/test/SAS_server_app.ts
Normal file
38
src/test/SAS_server_app.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import express = require('express')
|
||||
|
||||
export const app = express()
|
||||
|
||||
export const mockedAuthResponse = {
|
||||
access_token: 'access_token',
|
||||
token_type: 'bearer',
|
||||
id_token: 'id_token',
|
||||
refresh_token: 'refresh_token',
|
||||
expires_in: 43199,
|
||||
scope: 'openid',
|
||||
jti: 'jti'
|
||||
}
|
||||
|
||||
app.get('/', function (req: any, res: any) {
|
||||
res.send('Hello World')
|
||||
})
|
||||
|
||||
app.post('/SASLogon/oauth/token', function (req: any, res: any) {
|
||||
let valid = true
|
||||
// capture the encoded form data
|
||||
req.on('data', (data: any) => {
|
||||
const resData = data.toString()
|
||||
|
||||
if (resData.includes('incorrect')) valid = false
|
||||
})
|
||||
|
||||
// send a response when finished reading
|
||||
// the encoded form data
|
||||
req.on('end', () => {
|
||||
if (valid) res.status(200).send(mockedAuthResponse)
|
||||
else
|
||||
res.status(401).send({
|
||||
error: 'unauthorized',
|
||||
error_description: 'Bad credentials'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,5 @@
|
||||
import { SessionManager } from '../SessionManager'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import { NoSessionStateError } from '../types/errors'
|
||||
import * as dotenv from 'dotenv'
|
||||
import axios from 'axios'
|
||||
import { Logger, LogLevel } from '@sasjs/utils'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface ExecutionQuery {
|
||||
_program: string
|
||||
_debug?: number
|
||||
_log?: boolean
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as https from 'https'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
|
||||
/**
|
||||
@@ -11,6 +12,11 @@ export class SASjsConfig {
|
||||
* streamed.
|
||||
*/
|
||||
serverUrl: string = ''
|
||||
/**
|
||||
* The location of the STP Process Web Application. By default the adapter
|
||||
* will use '/SASjsApi/stp/execute' on SAS JS.
|
||||
*/
|
||||
pathSASJS?: string = ''
|
||||
/**
|
||||
* The location of the Stored Process Web Application. By default the adapter
|
||||
* will use '/SASStoredProcess/do' on SAS 9.
|
||||
@@ -54,11 +60,11 @@ export class SASjsConfig {
|
||||
*/
|
||||
useComputeApi: boolean | null = null
|
||||
/**
|
||||
* Defaults to `false`.
|
||||
* When set to `true`, the adapter will allow requests to SAS servers that use a self-signed SSL certificate.
|
||||
* Changing this setting is not recommended.
|
||||
* Optional settings to configure HTTPS Agent.
|
||||
* By providing `key`, `cert`, `ca` to connect with server
|
||||
* Other options can be set `rejectUnauthorized` and `requestCert`
|
||||
*/
|
||||
allowInsecureRequests = false
|
||||
httpsAgentOptions?: https.AgentOptions
|
||||
/**
|
||||
* Supported login mechanisms are - Redirected and Default
|
||||
*/
|
||||
|
||||
7
src/utils/createAxiosInstance.ts
Normal file
7
src/utils/createAxiosInstance.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import axios from 'axios'
|
||||
import * as https from 'https'
|
||||
|
||||
export const createAxiosInstance = (
|
||||
baseURL: string,
|
||||
httpsAgent?: https.Agent
|
||||
) => axios.create({ baseURL, httpsAgent })
|
||||
@@ -4,7 +4,7 @@ import { JsonParseArrayError, InvalidJsonError } from '../types/errors'
|
||||
* if string passed then parse the string to json else if throw error for all other types unless it is not a valid json object.
|
||||
* @param str - string to check.
|
||||
*/
|
||||
export const getValidJson = (str: string | object) => {
|
||||
export const getValidJson = (str: string | object): object => {
|
||||
try {
|
||||
if (str === null || str === undefined) throw new InvalidJsonError()
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './asyncForEach'
|
||||
export * from './compareTimestamps'
|
||||
export * from './convertToCsv'
|
||||
export * from './createAxiosInstance'
|
||||
export * from './delay'
|
||||
export * from './isNode'
|
||||
export * from './isRelativePath'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WeboutResponseError } from '../types/errors'
|
||||
|
||||
export const parseWeboutResponse = (response: string, url?: string) => {
|
||||
export const parseWeboutResponse = (response: string, url?: string): string => {
|
||||
let sasResponse = ''
|
||||
|
||||
if (response.includes('>>weboutBEGIN<<')) {
|
||||
|
||||
Reference in New Issue
Block a user