1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-16 00:20:06 +00:00

chore(git): merge branch 'master' into update-dependencies

This commit is contained in:
Yury Shkoda
2021-11-01 14:45:38 +03:00
16 changed files with 18349 additions and 2726 deletions

View File

@@ -13,14 +13,15 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [15.x] node-version: [lts/*]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: npm
- name: Install Dependencies - name: Install Dependencies
run: npm ci run: npm ci
- name: Check code style - name: Check code style

20769
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -41,17 +41,21 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
"@types/express": "^4.17.13",
"@types/form-data": "^2.5.0", "@types/form-data": "^2.5.0",
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@types/mime": "^2.0.3", "@types/mime": "^2.0.3",
"@types/pem": "^1.9.6",
"@types/tough-cookie": "^4.0.1", "@types/tough-cookie": "^4.0.1",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"cp": "^0.2.0", "cp": "^0.2.0",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"express": "^4.17.1",
"jest": "^27.2.0", "jest": "^27.2.0",
"jest-extended": "^0.11.5", "jest-extended": "^0.11.5",
"node-polyfill-webpack-plugin": "^1.1.4", "node-polyfill-webpack-plugin": "^1.1.4",
"path": "^0.12.7", "path": "^0.12.7",
"pem": "^1.14.4",
"process": "^0.11.10", "process": "^0.11.10",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semantic-release": "^18.0.0", "semantic-release": "^18.0.0",

View File

@@ -13,7 +13,6 @@ const defaultConfig: SASjsConfig = {
debug: false, debug: false,
contextName: 'SAS Job Execution compute context', contextName: 'SAS Job Execution compute context',
useComputeApi: false, useComputeApi: false,
allowInsecureRequests: false,
loginMechanism: LoginMechanism.Default loginMechanism: LoginMechanism.Default
} }

View File

@@ -1,3 +1,4 @@
import * as https from 'https'
import { generateTimestamp } from '@sasjs/utils/time' import { generateTimestamp } from '@sasjs/utils/time'
import * as NodeFormData from 'form-data' import * as NodeFormData from 'form-data'
import { Sas9RequestClient } from './request/Sas9RequestClient' import { Sas9RequestClient } from './request/Sas9RequestClient'
@@ -13,10 +14,10 @@ export class SAS9ApiClient {
constructor( constructor(
private serverUrl: string, private serverUrl: string,
private jobsPath: string, private jobsPath: string,
allowInsecureRequests: boolean httpsAgentOptions?: https.AgentOptions
) { ) {
if (serverUrl) isUrl(serverUrl) if (serverUrl) isUrl(serverUrl)
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests) this.requestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
} }
/** /**

View File

@@ -40,7 +40,6 @@ const defaultConfig: SASjsConfig = {
debug: false, debug: false,
contextName: 'SAS Job Execution compute context', contextName: 'SAS Job Execution compute context',
useComputeApi: null, useComputeApi: null,
allowInsecureRequests: false,
loginMechanism: LoginMechanism.Default loginMechanism: LoginMechanism.Default
} }
@@ -62,7 +61,7 @@ export default class SASjs {
private jesJobExecutor: JobExecutor | null = null private jesJobExecutor: JobExecutor | null = null
private sas9JobExecutor: JobExecutor | null = null private sas9JobExecutor: JobExecutor | null = null
constructor(config?: any) { constructor(config?: Partial<SASjsConfig>) {
this.sasjsConfig = { this.sasjsConfig = {
...defaultConfig, ...defaultConfig,
...config ...config
@@ -797,7 +796,7 @@ export default class SASjs {
sasApiClient = new SAS9ApiClient( sasApiClient = new SAS9ApiClient(
serverUrl, serverUrl,
this.jobsPath, this.jobsPath,
this.sasjsConfig.allowInsecureRequests this.sasjsConfig.httpsAgentOptions
) )
} }
} else { } else {
@@ -964,12 +963,12 @@ export default class SASjs {
if (!this.requestClient) { if (!this.requestClient) {
this.requestClient = new RequestClient( this.requestClient = new RequestClient(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.sasjsConfig.allowInsecureRequests this.sasjsConfig.httpsAgentOptions
) )
} else { } else {
this.requestClient.setConfig( this.requestClient.setConfig(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.sasjsConfig.allowInsecureRequests this.sasjsConfig.httpsAgentOptions
) )
} }
@@ -1010,7 +1009,7 @@ export default class SASjs {
this.sas9ApiClient = new SAS9ApiClient( this.sas9ApiClient = new SAS9ApiClient(
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.jobsPath, this.jobsPath,
this.sasjsConfig.allowInsecureRequests this.sasjsConfig.httpsAgentOptions
) )
} }
} }
@@ -1045,7 +1044,7 @@ export default class SASjs {
this.sasjsConfig.serverUrl, this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!, this.sasjsConfig.serverType!,
this.jobsPath, this.jobsPath,
this.sasjsConfig.allowInsecureRequests this.sasjsConfig.httpsAgentOptions
) )
this.computeJobExecutor = new ComputeJobExecutor( this.computeJobExecutor = new ComputeJobExecutor(

View File

@@ -46,7 +46,7 @@ export async function getAccessToken(
) )
.then((res) => res.result as SasAuthResponse) .then((res) => res.result as SasAuthResponse)
.catch((err) => { .catch((err) => {
throw prefixMessage(err, 'Error while getting access token') throw prefixMessage(err, 'Error while getting access token. ')
}) })
return authResponse return authResponse

View File

@@ -1,3 +1,4 @@
import * as https from 'https'
import { ServerType } from '@sasjs/utils/types' import { ServerType } from '@sasjs/utils/types'
import * as NodeFormData from 'form-data' import * as NodeFormData from 'form-data'
import { ErrorResponse } from '../types/errors' import { ErrorResponse } from '../types/errors'
@@ -17,10 +18,10 @@ export class Sas9JobExecutor extends BaseJobExecutor {
serverUrl: string, serverUrl: string,
serverType: ServerType, serverType: ServerType,
private jobsPath: string, private jobsPath: string,
allowInsecureRequests: boolean httpsAgentOptions?: https.AgentOptions
) { ) {
super(serverUrl, serverType) super(serverUrl, serverType)
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests) this.requestClient = new Sas9RequestClient(serverUrl, httpsAgentOptions)
} }
async execute(sasJob: string, data: any, config: any) { async execute(sasJob: string, data: any, config: any) {

View File

@@ -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 { CsrfToken } from '..'
import { isAuthorizeFormRequired, isLogInRequired } from '../auth' import { isAuthorizeFormRequired, isLogInRequired } from '../auth'
import { import {
@@ -12,7 +13,11 @@ import { SASjsRequest } from '../types'
import { parseWeboutResponse } from '../utils/parseWeboutResponse' import { parseWeboutResponse } from '../utils/parseWeboutResponse'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import { SAS9AuthError } from '../types/errors/SAS9AuthError' import { SAS9AuthError } from '../types/errors/SAS9AuthError'
import { parseGeneratedCode, parseSourceCode } from '../utils' import {
parseGeneratedCode,
parseSourceCode,
createAxiosInstance
} from '../utils'
export interface HttpClient { export interface HttpClient {
get<T>( get<T>(
@@ -54,12 +59,15 @@ export class RequestClient implements HttpClient {
protected fileUploadCsrfToken: CsrfToken | undefined protected fileUploadCsrfToken: CsrfToken | undefined
protected httpClient!: AxiosInstance protected httpClient!: AxiosInstance
constructor(protected baseUrl: string, allowInsecure = false) { constructor(
this.createHttpClient(baseUrl, allowInsecure) protected baseUrl: string,
httpsAgentOptions?: https.AgentOptions
) {
this.createHttpClient(baseUrl, httpsAgentOptions)
} }
public setConfig(baseUrl: string, allowInsecure = false) { public setConfig(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
this.createHttpClient(baseUrl, allowInsecure) this.createHttpClient(baseUrl, httpsAgentOptions)
} }
public getCsrfToken(type: 'general' | 'file' = 'general') { public getCsrfToken(type: 'general' | 'file' = 'general') {
@@ -511,20 +519,15 @@ export class RequestClient implements HttpClient {
return responseToReturn return responseToReturn
} }
private createHttpClient(baseUrl: string, allowInsecure = false) { private createHttpClient(
const https = require('https') baseUrl: string,
if (allowInsecure && https.Agent) { httpsAgentOptions?: https.AgentOptions
this.httpClient = axios.create({ ) {
baseURL: baseUrl, const httpsAgent = httpsAgentOptions
httpsAgent: new https.Agent({ ? new https.Agent(httpsAgentOptions)
rejectUnauthorized: !allowInsecure : undefined
})
}) this.httpClient = createAxiosInstance(baseUrl, httpsAgent)
} else {
this.httpClient = axios.create({
baseURL: baseUrl
})
}
this.httpClient.defaults.validateStatus = (status) => this.httpClient.defaults.validateStatus = (status) =>
status >= 200 && status < 401 status >= 200 && status < 401

View File

@@ -1,3 +1,4 @@
import * as https from 'https'
import { AxiosRequestConfig } from 'axios' import { AxiosRequestConfig } from 'axios'
import axiosCookieJarSupport from 'axios-cookiejar-support' import axiosCookieJarSupport from 'axios-cookiejar-support'
import * as tough from 'tough-cookie' import * as tough from 'tough-cookie'
@@ -9,8 +10,8 @@ import { RequestClient, throwIfError } from './RequestClient'
* Handles redirects and cookie management. * Handles redirects and cookie management.
*/ */
export class Sas9RequestClient extends RequestClient { export class Sas9RequestClient extends RequestClient {
constructor(baseUrl: string, allowInsecure = false) { constructor(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
super(baseUrl, allowInsecure) super(baseUrl, httpsAgentOptions)
this.httpClient.defaults.maxRedirects = 0 this.httpClient.defaults.maxRedirects = 0
this.httpClient.defaults.validateStatus = (status) => this.httpClient.defaults.validateStatus = (status) =>
status >= 200 && status < 303 status >= 200 && status < 303

View 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)
}
)
})
}

View 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'
})
})
})

View File

@@ -1,6 +1,5 @@
import { SessionManager } from '../SessionManager' import { SessionManager } from '../SessionManager'
import { RequestClient } from '../request/RequestClient' import { RequestClient } from '../request/RequestClient'
import { NoSessionStateError } from '../types/errors'
import * as dotenv from 'dotenv' import * as dotenv from 'dotenv'
import axios from 'axios' import axios from 'axios'
import { Logger, LogLevel } from '@sasjs/utils' import { Logger, LogLevel } from '@sasjs/utils'

View File

@@ -1,3 +1,4 @@
import * as https from 'https'
import { ServerType } from '@sasjs/utils/types' import { ServerType } from '@sasjs/utils/types'
/** /**
@@ -54,11 +55,11 @@ export class SASjsConfig {
*/ */
useComputeApi: boolean | null = null useComputeApi: boolean | null = null
/** /**
* Defaults to `false`. * Optional settings to configure HTTPS Agent.
* When set to `true`, the adapter will allow requests to SAS servers that use a self-signed SSL certificate. * By providing `key`, `cert`, `ca` to connect with server
* Changing this setting is not recommended. * Other options can be set `rejectUnauthorized` and `requestCert`
*/ */
allowInsecureRequests = false httpsAgentOptions?: https.AgentOptions
/** /**
* Supported login mechanisms are - Redirected and Default * Supported login mechanisms are - Redirected and Default
*/ */

View 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 })

View File

@@ -1,6 +1,7 @@
export * from './asyncForEach' export * from './asyncForEach'
export * from './compareTimestamps' export * from './compareTimestamps'
export * from './convertToCsv' export * from './convertToCsv'
export * from './createAxiosInstance'
export * from './delay' export * from './delay'
export * from './isNode' export * from './isNode'
export * from './isRelativePath' export * from './isRelativePath'