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

Compare commits

...

62 Commits

Author SHA1 Message Date
Krishna Acondy 596c1de5cb fix(request-client): return Internal Server Error code in case of Stored Process Error 2021-12-12 20:37:19 +00:00
Allan Bowe b645d1495b Merge pull request #586 from sasjs/handle-sasjs-server-response
fix: handle sasjs server response
2021-12-08 09:09:42 +00:00
medjedovic 3a4a4c3460 chore: sasjs path optional 2021-12-06 17:44:08 +01:00
Saad Jutt 182de51f9b chore: added comments 2021-12-05 19:27:07 +05:00
Saad Jutt 712d1549c7 feat(server): added sasjs server support 2021-12-05 18:31:36 +05:00
Allan Bowe 5a478c8936 Merge pull request #594 from sasjs/issue-588
fix: sas9JobExecutor issues
2021-12-03 11:13:15 +00:00
medjedovic c9ecc1dde4 chore: requestClient naming 2021-12-03 11:54:52 +01:00
medjedovic bdf9e2fd5b fix: sas9JobExecutor not returning response 2021-11-26 15:32:10 +01:00
Vladislav Parhomchik 0b795b26c0 Merge pull request #589 from sasjs/issue-587
Pinned typedoc version to 0.19.2
2021-11-26 12:47:21 +03:00
medjedovic 96aac0cfa2 fix: Sas9JobExecutor appendRequest 2021-11-24 20:26:02 +01:00
Yury Shkoda a82e1f33e3 docs: update docs 2021-11-24 11:23:27 +03:00
Yury Shkoda 058c887cd3 chore(docs): pinned typedoc version 2021-11-24 11:19:41 +03:00
Allan Bowe 81d959c7c1 fix: parsing _webout from response regardless of debug status 2021-11-18 19:56:12 +00:00
sabhas 7c5adeabb5 feat: parse response in webJobExecutor when server type is sasjs server 2021-11-19 00:35:02 +05:00
Yury Shkoda cb88376bda Merge pull request #576 from sasjs/update-dependencies
chore(deps): update dependencies
2021-11-01 15:50:51 +03:00
Yury Shkoda 4c8ddeca25 chore(deps): regenerate package-lock with --legacy-peer-deps option 2021-11-01 15:26:31 +03:00
Yury Shkoda d264a3f239 chore(ci/cd): used node lts/fermium for GH actions 2021-11-01 14:58:27 +03:00
Yury Shkoda 840b1aa1bf chore(deps): regenerate package-lock.json 2021-11-01 14:47:48 +03:00
Yury Shkoda e26fd307c8 chore(git): merge branch 'master' into update-dependencies 2021-11-01 14:45:38 +03:00
Allan Bowe 62deaf9f03 Merge pull request #581 from sasjs/package-lock-and-node-version
fix: updated package-lock + updated node version for workflow
2021-10-29 09:35:50 +01:00
Saad Jutt 865bf71f7d fix: updated package-lock + updated node version for workflow 2021-10-29 13:32:59 +05:00
Allan Bowe 734c5bccaa Merge pull request #564 from sasjs/issue-555
BREAKING CHANGE: boolean allowInsecure is replaced with Https Agent Config
2021-10-29 09:12:56 +01:00
Saad Jutt bc82cb5f5e chore: TSDoc comment updated 2021-10-29 00:29:14 +05:00
Allan Bowe f25c76fdfd fix: merge 2021-10-28 13:50:29 +00:00
Allan Bowe e649b41e9e Merge pull request #579 from sasjs/sasjs/server-support
chore: removed legacy code
2021-10-28 14:02:17 +01:00
Yury Shkoda a962979765 chore: removed legacy code 2021-10-28 15:44:17 +03:00
Yury Shkoda 01d76fa66f Merge pull request #574 from sasjs/sasjs/server
Add sasjs/server support
2021-10-28 15:36:45 +03:00
Yury Shkoda 49cfde9f7d chore(sasjs/server): fix deploy endpoint 2021-10-28 15:13:14 +03:00
Yury Shkoda ce04ffea05 fix(SASjsApiClient): change SASjs Server endpoints 2021-10-28 10:21:15 +03:00
medjedovic 9dc0499f66 chore: allow insecure remove 2021-10-27 13:20:59 +02:00
Allan Bowe bc1a7dc54f chore: lint fix 2021-10-27 09:53:42 +00:00
Allan Bowe 93c267fd4e Update AuthManager.ts 2021-10-27 12:20:40 +03:00
Yury Shkoda 0457eb6663 fix: fix calls to SASjsApi endpoints 2021-10-27 11:16:35 +03:00
Yury Shkoda 519494718b chore: address PR comments 2021-10-27 11:15:28 +03:00
Vladislav Parhomchik de5c38f0fb chore(deps): versions fix, authmanager default returns 2021-10-26 15:46:51 +03:00
Vladislav Parhomchik 208470e7d9 chore(deps): update dependencies 2021-10-26 13:06:03 +03:00
Yury Shkoda 0321f77451 chore: update dependencies 2021-10-20 15:08:58 +03:00
Yury Shkoda 6c5fdc01eb chore: merge branch 'sasjs/server' of https://github.com/sasjs/adapter into sasjs/server 2021-10-20 15:05:03 +03:00
Yury Shkoda 2aa0cd8d7a chore: remove tmp utilities 2021-10-20 15:01:21 +03:00
Yury Shkoda 397bc4524f chore: change 'SASBase' to 'SASjs' 2021-10-20 15:00:52 +03:00
Yury Shkoda 8dce9f3e48 chore(npm): update @sasjs/utils version 2021-10-20 14:59:02 +03:00
medjedovic 8e9f1df1ce chore: fixing the error when using in angular, added isNode check for imports 2021-10-12 16:45:39 +02:00
Saad Jutt ff4915f7f3 chore: comment file removed 2021-10-07 13:47:28 +05:00
Saad Jutt 6ff8eece7b chore: removed httpsAgent type + clean up 2021-10-07 13:45:50 +05:00
Saad Jutt 2849e6ed07 test(RequestClient): updated 2021-10-06 14:06:00 +05:00
Yury Shkoda 90b11fe3fa feat(deploy): add appLoc 2021-10-04 17:01:25 +03:00
Yury Shkoda 147609842d Merge pull request #565 from sasjs/modifying-npmignore
chore: add .all-contributorsrc to .npmignore
2021-10-04 09:17:54 +03:00
Yury Shkoda dd6f9cd617 chore(npm): add empty line to the end of .npmignore 2021-10-04 09:10:37 +03:00
Saad Jutt 7f590c35da test(RequestClient): fixed to use actual axios 2021-10-01 15:42:04 +05:00
Vladislav Parhomchik a38de108e3 chore: add .all-contributorsrc to .npmignore 2021-10-01 10:31:30 +03:00
Saad Jutt e975e7de97 test(RequestClient): specs for communicating with self-signed server 2021-10-01 12:03:44 +05:00
Yury Shkoda d418a7e971 fix(http): extend valid responce statuses up to 400 2021-09-30 14:33:33 +03:00
Yury Shkoda a5b5052a5f fix(baseSAS): removed sasjs/server logic 2021-09-30 14:31:54 +03:00
Yury Shkoda 7638595523 chore(git): fix .gitignore 2021-09-30 14:30:47 +03:00
Allan Bowe 70d64f6eec Merge pull request #556 from sasjs/test-framework
chore: bump up test-framework
2021-09-29 22:42:54 +01:00
Saad Jutt f0ecfa57e5 BREAKING CHANGE: boolean allowInsecure is replaced configuration of Https Agent 2021-09-28 16:02:12 +05:00
Yury Shkoda 5f3416ecd7 chore(utils): add tmpFolder utils 2021-09-28 10:39:54 +03:00
Yury Shkoda d8b1a72da2 chore(types): add FileTree types 2021-09-28 10:39:18 +03:00
Yury Shkoda 7e64819eb2 feat(sasjs/server): add SASBaseApiClient class 2021-09-28 10:38:29 +03:00
Yury Shkoda 2f1d403af4 chore(deps): use @sasjs/utils tarball 2021-09-28 10:33:11 +03:00
Yury Shkoda 075d410f7d chore(git): ignore tmp folder 2021-09-28 10:31:36 +03:00
medjedovic 085a3f84e9 chore: bump up test-framework 2021-09-16 10:37:56 +02:00
104 changed files with 29784 additions and 3881 deletions
+3 -2
View File
@@ -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
+1 -1
View File
@@ -5,4 +5,4 @@ build
/coverage
.DS_Store
.DS_Store
+1
View File
@@ -3,3 +3,4 @@ docs/
.github/
*.md
*.spec.ts
.all-contributorsrc
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
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
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
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
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
+12 -2
View File
File diff suppressed because one or more lines are too long
+97 -42
View 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
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
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
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
+286 -2
View 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
File diff suppressed because one or more lines are too long
+302 -9
View File
File diff suppressed because one or more lines are too long
+17829 -2728
View File
File diff suppressed because it is too large Load Diff
+10 -6
View File
@@ -41,35 +41,39 @@
"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",
"dependencies": {
"@sasjs/utils": "^2.30.0",
"@sasjs/utils": "^2.32.0",
"axios": "^0.21.4",
"axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0",
+1 -1
View File
@@ -5,7 +5,7 @@
"private": true,
"dependencies": {
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
"@sasjs/test-framework": "^1.4.0",
"@sasjs/test-framework": "^1.4.2",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.41",
"@types/react": "^17.0.1",
-1
View File
@@ -13,7 +13,6 @@ const defaultConfig: SASjsConfig = {
debug: false,
contextName: 'SAS Job Execution compute context',
useComputeApi: false,
allowInsecureRequests: false,
loginMechanism: LoginMechanism.Default
}
+3 -2
View File
@@ -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)
}
/**
+49 -17
View File
@@ -4,10 +4,14 @@ import {
UploadFile,
EditContextInput,
PollOptions,
LoginMechanism
LoginMechanism,
FolderMember,
ServiceMember,
ExecutionQuery
} from './types'
import { SASViyaApiClient } from './SASViyaApiClient'
import { SAS9ApiClient } from './SAS9ApiClient'
import { SASjsApiClient } from './SASjsApiClient'
import { AuthManager } from './auth'
import {
ServerType,
@@ -29,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',
@@ -36,7 +41,6 @@ const defaultConfig: SASjsConfig = {
debug: false,
contextName: 'SAS Job Execution compute context',
useComputeApi: null,
allowInsecureRequests: false,
loginMechanism: LoginMechanism.Default
}
@@ -49,6 +53,7 @@ export default class SASjs {
private jobsPath: string = ''
private sasViyaApiClient: SASViyaApiClient | null = null
private sas9ApiClient: SAS9ApiClient | null = null
private SASjsApiClient: SASjsApiClient | null = null
private fileUploader: FileUploader | null = null
private authManager: AuthManager | null = null
private requestClient: RequestClient | null = null
@@ -57,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
@@ -523,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)
}
/**
@@ -559,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)
}
/**
@@ -792,7 +799,7 @@ export default class SASjs {
sasApiClient = new SAS9ApiClient(
serverUrl,
this.jobsPath,
this.sasjsConfig.allowInsecureRequests
this.sasjsConfig.httpsAgentOptions
)
}
} else {
@@ -824,6 +831,14 @@ export default class SASjs {
)
}
public async deployToSASjs(members: [FolderMember, ServiceMember]) {
return await this.SASjsApiClient?.deploy(members, this.sasjsConfig.appLoc)
}
public async executeJobSASjs(query: ExecutionQuery) {
return await this.SASjsApiClient?.executeJob(query)
}
/**
* Kicks off execution of the given job via the compute API.
* @returns an object representing the compute session created for the given job.
@@ -951,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,
@@ -973,30 +990,44 @@ export default class SASjs {
)
if (this.sasjsConfig.serverType === ServerType.SasViya) {
if (this.sasViyaApiClient)
if (this.sasViyaApiClient) {
this.sasViyaApiClient!.setConfig(
this.sasjsConfig.serverUrl,
this.sasjsConfig.appLoc
)
else
} else {
this.sasViyaApiClient = new SASViyaApiClient(
this.sasjsConfig.serverUrl,
this.sasjsConfig.appLoc,
this.sasjsConfig.contextName,
this.requestClient
)
}
this.sasViyaApiClient.debug = this.sasjsConfig.debug
}
if (this.sasjsConfig.serverType === ServerType.Sas9) {
if (this.sas9ApiClient)
if (this.sas9ApiClient) {
this.sas9ApiClient!.setConfig(this.sasjsConfig.serverUrl)
else
} else {
this.sas9ApiClient = new SAS9ApiClient(
this.sasjsConfig.serverUrl,
this.jobsPath,
this.sasjsConfig.allowInsecureRequests
this.sasjsConfig.httpsAgentOptions
)
}
}
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
if (this.SASjsApiClient) {
this.SASjsApiClient.setConfig(this.sasjsConfig.serverUrl)
} else {
this.SASjsApiClient = new SASjsApiClient(
this.sasjsConfig.serverUrl,
this.requestClient
)
}
}
this.fileUploader = new FileUploader(
@@ -1018,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(
+39
View File
@@ -0,0 +1,39 @@
import { FolderMember, ServiceMember, ExecutionQuery } from './types'
import { RequestClient } from './request/RequestClient'
export class SASjsApiClient {
constructor(
private serverUrl: string,
private requestClient: RequestClient
) {}
public setConfig(serverUrl: string) {
if (serverUrl) this.serverUrl = serverUrl
}
public async deploy(members: [FolderMember, ServiceMember], appLoc: string) {
const { result } = await this.requestClient.post<{
status: string
message: string
example?: {}
}>(
'SASjsApi/drive/deploy',
{ fileTree: members, appLoc: appLoc },
undefined
)
return Promise.resolve(result)
}
public async executeJob(query: ExecutionQuery) {
const { result } = await this.requestClient.post<{
status: string
message: string
log?: string
logPath?: string
error?: {}
}>('SASjsApi/stp/execute', query, undefined)
return Promise.resolve(result)
}
}
+25 -10
View File
@@ -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,6 +260,13 @@ 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 ''
}
}
@@ -302,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)
}
+1 -1
View File
@@ -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
+36 -5
View File
@@ -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 {
+18 -3
View File
@@ -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:
+25 -22
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 { 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,23 +519,18 @@ 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 < 305
status >= 200 && status < 401
}
}
@@ -624,7 +627,7 @@ const parseError = (data: string) => {
if (parts.length > 1) {
const log = parts[1].split('<pre>')[1].split('</pre>')[0]
const message = `This request completed with errors.`
return new JobExecutionError(404, message, log)
return new JobExecutionError(500, message, log)
}
}
} catch (_) {}
+3 -2
View File
@@ -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
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)
}
)
})
}
+38
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'
})
})
})
-1
View File
@@ -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'
+4
View File
@@ -0,0 +1,4 @@
export interface ExecutionQuery {
_program: string
_debug?: number
}
+47
View File
@@ -0,0 +1,47 @@
export interface FileTree {
members: [FolderMember, ServiceMember]
}
export enum MemberType {
folder = 'folder',
service = 'service'
}
export interface FolderMember {
name: string
type: MemberType.folder
members: [FolderMember, ServiceMember]
}
export interface ServiceMember {
name: string
type: MemberType.service
code: string
}
export const isFileTree = (arg: any): arg is FileTree =>
arg &&
arg.members &&
Array.isArray(arg.members) &&
arg.members.filter(
(member: FolderMember | ServiceMember) =>
!isFolderMember(member) && !isServiceMember(member)
).length === 0
const isFolderMember = (arg: any): arg is FolderMember =>
arg &&
typeof arg.name === 'string' &&
arg.type === MemberType.folder &&
arg.members &&
Array.isArray(arg.members) &&
arg.members.filter(
(member: FolderMember | ServiceMember) =>
!isFolderMember(member) && !isServiceMember(member)
).length === 0
const isServiceMember = (arg: any): arg is ServiceMember =>
arg &&
typeof arg.name === 'string' &&
arg.type === MemberType.service &&
arg.code &&
typeof arg.code === 'string'
+10 -4
View File
@@ -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
*/
+2
View File
@@ -12,3 +12,5 @@ export * from './Session'
export * from './UploadFile'
export * from './PollOptions'
export * from './WriteStream'
export * from './FileTree'
export * from './ExecuteScript'

Some files were not shown because too many files have changed in this diff Show More