From 03b5e1d82409f3325c9881be75ae999180be99e3 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Tue, 2 Feb 2021 08:18:48 +0000 Subject: [PATCH] chore(*): refactor common functionality into JobExecutor, handle all auth-related scenarios --- src/SASViyaApiClient.ts | 6 +- src/SASjs.ts | 1 + src/auth/AuthManager.ts | 44 ++++---- src/auth/index.ts | 3 - src/auth/parseAndSubmitAuthorizeForm.ts | 48 -------- src/auth/spec/AuthManager.spec.ts | 73 +++++++++--- src/job-execution/ComputeJobExecutor.ts | 91 ++------------- src/job-execution/JesJobExecutor.ts | 85 ++------------ src/job-execution/JobExecutor.ts | 91 ++++++++++++++- src/job-execution/WebJobExecutor.ts | 96 ++-------------- src/request/RequestClient.ts | 144 +++++++++++++++++++----- 11 files changed, 307 insertions(+), 375 deletions(-) delete mode 100644 src/auth/parseAndSubmitAuthorizeForm.ts diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index c9ae9bf..ae8f8d3 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -18,7 +18,6 @@ import { ContextManager } from './ContextManager' import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time' import { Logger, LogLevel } from '@sasjs/utils/logger' import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired' -import { parseAndSubmitAuthorizeForm } from './auth' import { RequestClient } from './request/RequestClient' import { NotFoundError } from './types/NotFoundError' @@ -632,10 +631,7 @@ export class SASViyaApiClient { .then(async (response) => { let code = '' if (isAuthorizeFormRequired(response)) { - const formResponse: any = await parseAndSubmitAuthorizeForm( - response, - this.serverUrl - ) + const formResponse: any = await this.requestClient.authorize(response) const responseBody = formResponse .split('')[1] diff --git a/src/SASjs.ts b/src/SASjs.ts index c3b27ff..d411a9e 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -716,6 +716,7 @@ export default class SASjs { this.authManager = new AuthManager( this.sasjsConfig.serverUrl, this.sasjsConfig.serverType!, + this.requestClient, this.resendWaitingRequests ) diff --git a/src/auth/AuthManager.ts b/src/auth/AuthManager.ts index 1d1f315..e40c7ed 100644 --- a/src/auth/AuthManager.ts +++ b/src/auth/AuthManager.ts @@ -1,19 +1,18 @@ import { ServerType } from '@sasjs/utils/types' -import axios, { AxiosInstance } from 'axios' -import { isAuthorizeFormRequired, parseAndSubmitAuthorizeForm } from '.' +import { isAuthorizeFormRequired } from '.' +import { RequestClient } from '../request/RequestClient' import { serialize } from '../utils' export class AuthManager { public userName = '' private loginUrl: string private logoutUrl: string - private httpClient: AxiosInstance constructor( private serverUrl: string, private serverType: ServerType, + private requestClient: RequestClient, private loginCallback: () => Promise ) { - this.httpClient = axios.create({ baseURL: this.serverUrl }) this.loginUrl = `/SASLogon/login` this.logoutUrl = this.serverType === ServerType.Sas9 @@ -50,21 +49,21 @@ export class AuthManager { } const loginParamsStr = serialize(loginParams) - const loginResponse = await this.httpClient - .post(this.loginUrl, loginParamsStr, { - withCredentials: true, - responseType: 'text', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Accept: '*/*' - } - }) - .then((response) => response.data) + const { result: loginResponse } = await this.requestClient.post( + this.loginUrl, + loginParamsStr, + undefined, + 'text/plain', + { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: '*/*' + } + ) let loggedIn if (isAuthorizeFormRequired(loginResponse)) { - await parseAndSubmitAuthorizeForm(loginResponse, this.serverUrl) + await this.requestClient.authorize(loginResponse) } else { loggedIn = isLogInSuccess(loginResponse) } @@ -89,16 +88,12 @@ export class AuthManager { * @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`. */ public async checkSession() { - const loginResponse = await this.httpClient.get( + const { result: loginResponse } = await this.requestClient.get( this.loginUrl.replace('.do', ''), - { - responseType: 'text', - headers: { - Accept: '*/*' - } - } + undefined, + 'text/plain' ) - const responseText = loginResponse?.data + const responseText = loginResponse const isLoggedIn = / true) + this.requestClient.clearCsrfTokens() + return this.requestClient.get(this.logoutUrl, undefined).then(() => true) } } diff --git a/src/auth/index.ts b/src/auth/index.ts index 9641d3f..b2d09d0 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -1,6 +1,3 @@ -import { AuthManager } from './AuthManager' - export * from './AuthManager' export * from './isAuthorizeFormRequired' export * from './isLoginRequired' -export * from './parseAndSubmitAuthorizeForm' diff --git a/src/auth/parseAndSubmitAuthorizeForm.ts b/src/auth/parseAndSubmitAuthorizeForm.ts deleted file mode 100644 index 28f342a..0000000 --- a/src/auth/parseAndSubmitAuthorizeForm.ts +++ /dev/null @@ -1,48 +0,0 @@ -import axios from 'axios' - -export const parseAndSubmitAuthorizeForm = async ( - response: string, - serverUrl: string -) => { - let authUrl: string | null = null - const params: any = {} - - const responseBody = response.split('')[1].split('')[0] - const bodyElement = document.createElement('div') - bodyElement.innerHTML = responseBody - - const form = bodyElement.querySelector('#application_authorization') - authUrl = form ? serverUrl + form.getAttribute('action') : null - - const inputs: any = form?.querySelectorAll('input') - - for (const input of inputs) { - if (input.name === 'user_oauth_approval') { - input.value = 'true' - } - - params[input.name] = input.value - } - - const formData = new FormData() - - for (const key in params) { - if (params.hasOwnProperty(key)) { - formData.append(key, params[key]) - } - } - - if (!authUrl) { - throw new Error('Auth Form URL is null or undefined.') - } - - return await axios - .post(authUrl, formData, { - withCredentials: true, - responseType: 'text', - headers: { - Accept: '*/*' - } - }) - .then((res) => res.data) -} diff --git a/src/auth/spec/AuthManager.spec.ts b/src/auth/spec/AuthManager.spec.ts index 111319e..71d5c2d 100644 --- a/src/auth/spec/AuthManager.spec.ts +++ b/src/auth/spec/AuthManager.spec.ts @@ -1,6 +1,5 @@ import { AuthManager } from '../AuthManager' import * as dotenv from 'dotenv' -import * as authModule from '..' import { ServerType } from '@sasjs/utils/types' import axios from 'axios' import { @@ -8,8 +7,8 @@ import { mockLoginSuccessResponse } from './mockResponses' import { serialize } from '../../utils' +import { RequestClient } from '../../request/RequestClient' jest.mock('axios') -jest.mock('../parseAndSubmitAuthorizeForm') const mockedAxios = axios as jest.Mocked describe('AuthManager', () => { @@ -18,6 +17,7 @@ describe('AuthManager', () => { const serverType = ServerType.SasViya const userName = 'test-username' const password = 'test-password' + const requestClient = new RequestClient(serverUrl) beforeAll(() => { dotenv.config() @@ -25,12 +25,16 @@ describe('AuthManager', () => { }) it('should instantiate and set the correct URLs for a Viya server', () => { - const authManager = new AuthManager(serverUrl, serverType, authCallback) + const authManager = new AuthManager( + serverUrl, + serverType, + requestClient, + authCallback + ) expect(authManager).toBeTruthy() expect((authManager as any).serverUrl).toEqual(serverUrl) expect((authManager as any).serverType).toEqual(serverType) - expect((authManager as any).httpClient).toBeTruthy() expect((authManager as any).loginUrl).toEqual(`/SASLogon/login`) expect((authManager as any).logoutUrl).toEqual('/SASLogon/logout.do?') }) @@ -39,18 +43,27 @@ describe('AuthManager', () => { const authCallback = () => Promise.resolve() const serverType = ServerType.Sas9 - const authManager = new AuthManager(serverUrl, serverType, authCallback) + const authManager = new AuthManager( + serverUrl, + serverType, + requestClient, + authCallback + ) expect(authManager).toBeTruthy() expect((authManager as any).serverUrl).toEqual(serverUrl) expect((authManager as any).serverType).toEqual(serverType) - expect((authManager as any).httpClient).toBeTruthy() expect((authManager as any).loginUrl).toEqual(`/SASLogon/login`) expect((authManager as any).logoutUrl).toEqual('/SASLogon/logout?') }) it('should call the auth callback and return when already logged in', async (done) => { - const authManager = new AuthManager(serverUrl, serverType, authCallback) + const authManager = new AuthManager( + serverUrl, + serverType, + requestClient, + authCallback + ) jest.spyOn(authManager, 'checkSession').mockImplementation(() => Promise.resolve({ isLoggedIn: true, @@ -68,7 +81,12 @@ describe('AuthManager', () => { }) it('should post a login request to the server if not logged in', async (done) => { - const authManager = new AuthManager(serverUrl, serverType, authCallback) + const authManager = new AuthManager( + serverUrl, + serverType, + requestClient, + authCallback + ) jest.spyOn(authManager, 'checkSession').mockImplementation(() => Promise.resolve({ isLoggedIn: false, @@ -96,7 +114,6 @@ describe('AuthManager', () => { loginParams, { withCredentials: true, - responseType: 'text', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: '*/*' @@ -108,9 +125,14 @@ describe('AuthManager', () => { }) it('should parse and submit the authorisation form when necessary', async (done) => { - const authManager = new AuthManager(serverUrl, serverType, authCallback) + const authManager = new AuthManager( + serverUrl, + serverType, + requestClient, + authCallback + ) jest - .spyOn(authModule, 'parseAndSubmitAuthorizeForm') + .spyOn(requestClient, 'authorize') .mockImplementation(() => Promise.resolve()) jest.spyOn(authManager, 'checkSession').mockImplementation(() => Promise.resolve({ @@ -125,15 +147,19 @@ describe('AuthManager', () => { await authManager.logIn(userName, password) - expect(authModule.parseAndSubmitAuthorizeForm).toHaveBeenCalledWith( - mockLoginAuthoriseRequiredResponse, - serverUrl + expect(requestClient.authorize).toHaveBeenCalledWith( + mockLoginAuthoriseRequiredResponse ) done() }) it('should check and return session information if logged in', async (done) => { - const authManager = new AuthManager(serverUrl, serverType, authCallback) + const authManager = new AuthManager( + serverUrl, + serverType, + requestClient, + authCallback + ) mockedAxios.get.mockImplementation(() => Promise.resolve({ data: '