fix(core): Allow ignoring SSL issues on generic oauth2 credentials (#6702)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-07-26 17:56:59 +02:00 committed by GitHub
parent db3c12ffc7
commit feac369f6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 36 additions and 8 deletions

View file

@ -4,7 +4,9 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import * as qs from 'querystring'; import * as qs from 'querystring';
import { Agent } from 'https';
import axios from 'axios'; import axios from 'axios';
import type { AxiosRequestConfig } from 'axios';
import { getAuthError } from './utils'; import { getAuthError } from './utils';
import type { ClientOAuth2TokenData } from './ClientOAuth2Token'; import type { ClientOAuth2TokenData } from './ClientOAuth2Token';
import { ClientOAuth2Token } from './ClientOAuth2Token'; import { ClientOAuth2Token } from './ClientOAuth2Token';
@ -18,6 +20,7 @@ export interface ClientOAuth2RequestObject {
body?: Record<string, any>; body?: Record<string, any>;
query?: qs.ParsedUrlQuery; query?: qs.ParsedUrlQuery;
headers?: Headers; headers?: Headers;
ignoreSSLIssues?: boolean;
} }
export interface ClientOAuth2Options { export interface ClientOAuth2Options {
@ -32,6 +35,7 @@ export interface ClientOAuth2Options {
state?: string; state?: string;
body?: Record<string, any>; body?: Record<string, any>;
query?: qs.ParsedUrlQuery; query?: qs.ParsedUrlQuery;
ignoreSSLIssues?: boolean;
} }
class ResponseError extends Error { class ResponseError extends Error {
@ -40,6 +44,8 @@ class ResponseError extends Error {
} }
} }
const sslIgnoringAgent = new Agent({ rejectUnauthorized: false });
/** /**
* Construct an object that can handle the multiple OAuth 2.0 flows. * Construct an object that can handle the multiple OAuth 2.0 flows.
*/ */
@ -86,7 +92,7 @@ export class ClientOAuth2 {
url += (url.indexOf('?') === -1 ? '?' : '&') + query; url += (url.indexOf('?') === -1 ? '?' : '&') + query;
} }
const response = await axios.request({ const requestConfig: AxiosRequestConfig = {
url, url,
method: options.method, method: options.method,
data: qs.stringify(options.body), data: qs.stringify(options.body),
@ -95,7 +101,13 @@ export class ClientOAuth2 {
// Axios rejects the promise by default for all status codes 4xx. // Axios rejects the promise by default for all status codes 4xx.
// We override this to reject promises only on 5xxs // We override this to reject promises only on 5xxs
validateStatus: (status) => status < 500, validateStatus: (status) => status < 500,
}); };
if (options.ignoreSSLIssues) {
requestConfig.httpsAgent = sslIgnoringAgent;
}
const response = await axios.request(requestConfig);
const body = this.parseResponseBody<T>(response.data); const body = this.parseResponseBody<T>(response.data);

View file

@ -53,13 +53,13 @@ export class CodeFlow {
* the user access token. * the user access token.
*/ */
async getToken( async getToken(
uri: string | URL, urlString: string,
opts?: Partial<ClientOAuth2Options>, opts?: Partial<ClientOAuth2Options>,
): Promise<ClientOAuth2Token> { ): Promise<ClientOAuth2Token> {
const options = { ...this.client.options, ...opts }; const options: ClientOAuth2Options = { ...this.client.options, ...opts };
expects(options, 'clientId', 'accessTokenUri'); expects(options, 'clientId', 'accessTokenUri');
const url = uri instanceof URL ? uri : new URL(uri, DEFAULT_URL_BASE); const url = new URL(urlString, DEFAULT_URL_BASE);
if ( if (
typeof options.redirectUri === 'string' && typeof options.redirectUri === 'string' &&
typeof url.pathname === 'string' && typeof url.pathname === 'string' &&
@ -70,7 +70,7 @@ export class CodeFlow {
if (!url.search?.substring(1)) { if (!url.search?.substring(1)) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new TypeError(`Unable to process uri: ${uri.toString()}`); throw new TypeError(`Unable to process uri: ${urlString}`);
} }
const data = const data =

View file

@ -63,14 +63,15 @@ export function auth(username: string, password: string): string {
*/ */
export function getRequestOptions( export function getRequestOptions(
{ url, method, body, query, headers }: ClientOAuth2RequestObject, { url, method, body, query, headers }: ClientOAuth2RequestObject,
options: any, options: ClientOAuth2Options,
): ClientOAuth2RequestObject { ): ClientOAuth2RequestObject {
const rOptions = { const rOptions = {
url, url,
method, method,
body: { ...body, ...options.body }, body: { ...body, ...options.body },
query: { ...query, ...options.query }, query: { ...query, ...options.query },
headers: { ...headers, ...options.headers }, headers: headers ?? {},
ignoreSSLIssues: options.ignoreSSLIssues,
}; };
// if request authorization was overridden delete it from header // if request authorization was overridden delete it from header
if (rOptions.headers.Authorization === '') { if (rOptions.headers.Authorization === '') {

View file

@ -265,6 +265,7 @@ oauth2CredentialController.get(
redirectUri: `${getInstanceBaseUrl()}/${restEndpoint}/oauth2-credential/callback`, redirectUri: `${getInstanceBaseUrl()}/${restEndpoint}/oauth2-credential/callback`,
scopes: split(scopes, ','), scopes: split(scopes, ','),
scopesSeparator: scopes.includes(',') ? ',' : ' ', scopesSeparator: scopes.includes(',') ? ',' : ' ',
ignoreSSLIssues: get(oauthCredentials, 'ignoreSSLIssues') as boolean,
}; };
if (oauthCredentials.grantType === 'pkce') { if (oauthCredentials.grantType === 'pkce') {

View file

@ -1092,6 +1092,7 @@ export async function requestOAuth2(
clientSecret: credentials.clientSecret as string, clientSecret: credentials.clientSecret as string,
accessTokenUri: credentials.accessTokenUrl as string, accessTokenUri: credentials.accessTokenUrl as string,
scopes: (credentials.scope as string).split(' '), scopes: (credentials.scope as string).split(' '),
ignoreSSLIssues: credentials.ignoreSSLIssues as boolean,
}); });
let oauthTokenData = credentials.oauthTokenData as ClientOAuth2TokenData; let oauthTokenData = credentials.oauthTokenData as ClientOAuth2TokenData;
@ -1131,6 +1132,9 @@ export async function requestOAuth2(
}, },
oAuth2Options?.tokenType || oauthTokenData.tokenType, oAuth2Options?.tokenType || oauthTokenData.tokenType,
); );
(requestOptions as OptionsWithUri).rejectUnauthorized = !credentials.ignoreSSLIssues;
// Signs the request by adding authorization headers or query parameters depending // Signs the request by adding authorization headers or query parameters depending
// on the token-type used. // on the token-type used.
const newRequestOptions = token.sign(requestOptions as ClientOAuth2RequestObject); const newRequestOptions = token.sign(requestOptions as ClientOAuth2RequestObject);

View file

@ -104,5 +104,12 @@ export class OAuth2Api implements ICredentialType {
], ],
default: 'header', default: 'header',
}, },
{
displayName: 'Ignore SSL Issues',
name: 'ignoreSSLIssues',
type: 'boolean',
default: false,
doNotInherit: true,
},
]; ];
} }

View file

@ -1116,6 +1116,7 @@ export interface INodeProperties {
extractValue?: INodePropertyValueExtractor; extractValue?: INodePropertyValueExtractor;
modes?: INodePropertyMode[]; modes?: INodePropertyMode[];
requiresDataPath?: 'single' | 'multiple'; requiresDataPath?: 'single' | 'multiple';
doNotInherit?: boolean;
} }
export interface INodePropertyModeTypeOptions { export interface INodePropertyModeTypeOptions {

View file

@ -1601,6 +1601,8 @@ export function mergeNodeProperties(
): void { ): void {
let existingIndex: number; let existingIndex: number;
for (const property of addProperties) { for (const property of addProperties) {
if (property.doNotInherit) continue;
existingIndex = mainProperties.findIndex((element) => element.name === property.name); existingIndex = mainProperties.findIndex((element) => element.name === property.name);
if (existingIndex === -1) { if (existingIndex === -1) {