Skip to content

Commit

Permalink
Merge branch 'main' into feat/1679/preview20
Browse files Browse the repository at this point in the history
  • Loading branch information
heimwege authored Jul 31, 2024
2 parents d71ac76 + 13d5ee5 commit 26ebcae
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 34 deletions.
7 changes: 7 additions & 0 deletions packages/cap-config-writer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @sap-ux/cap-config-writer

## 0.7.5

### Patch Changes

- Updated dependencies [eb1adcc]
- @sap-ux/odata-service-inquirer@0.5.5

## 0.7.4

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/cap-config-writer/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sap-ux/cap-config-writer",
"description": "Add or update configuration for SAP CAP projects",
"version": "0.7.4",
"version": "0.7.5",
"repository": {
"type": "git",
"url": "https://github.com/SAP/open-ux-tools.git",
Expand Down
6 changes: 6 additions & 0 deletions packages/create/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @sap-ux/create

## 0.7.28

### Patch Changes

- @sap-ux/cap-config-writer@0.7.5

## 0.7.27

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/create/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sap-ux/create",
"description": "SAP Fiori tools module to add or remove features",
"version": "0.7.27",
"version": "0.7.28",
"repository": {
"type": "git",
"url": "https://github.com/SAP/open-ux-tools.git",
Expand Down
6 changes: 6 additions & 0 deletions packages/odata-service-inquirer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @sap-ux/odata-service-inquirer

## 0.5.5

### Patch Changes

- eb1adcc: Fixes multiple issues with Abap On Prem system creation

## 0.5.4

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/odata-service-inquirer/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sap-ux/odata-service-inquirer",
"description": "Prompts module that can prompt users for inputs required for odata service writing",
"version": "0.5.4",
"version": "0.5.5",
"repository": {
"type": "git",
"url": "https://github.com/SAP/open-ux-tools.git",
Expand Down
79 changes: 70 additions & 9 deletions packages/odata-service-inquirer/src/prompts/connectionValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ import { errorHandler } from './prompt-helpers';
* Structure to store validity information about url to be validated.
*/
interface Validity {
// True if the url is in a valid format
urlFormat?: boolean;
// True if the url is reachable
reachable?: boolean;
// True if the url requires authentication, i.e. returns a 401/403 (note even once authenticated, this will remain true)
authRequired?: boolean;
// True if the url is authenticated and accessible
authenticated?: boolean;
// True if the url has a cert error that can be skipped
canSkipCertError?: boolean;
}

Expand All @@ -41,7 +46,11 @@ const ignorableCertErrors = [ERROR_TYPE.CERT_SELF_SIGNED, ERROR_TYPE.CERT_SELF_S
*/
export class ConnectionValidator {
public readonly validity: Validity = {};
// The current valid url (not necessarily authenticated but the url is in a valid format)
private _validatedUrl: string | undefined;
// The current client code used for requests, the client code has been validated by a successful request
private _validatedClient: string | undefined;

private _odataService: ODataService;
private _serviceProvider: ServiceProvider;
private _axiosConfig: AxiosRequestConfig;
Expand Down Expand Up @@ -135,7 +144,7 @@ export class ConnectionValidator {
// Full service URL
await this.createServiceConnection(axiosConfig, url.pathname);
}

this._validatedClient = url.searchParams.get(SAP_CLIENT_KEY) ?? undefined;
return 200;
} catch (e) {
LoggerHelper.logger.debug(`ConnectionValidator.checkSapService() - error: ${e.message}`);
Expand Down Expand Up @@ -236,7 +245,7 @@ export class ConnectionValidator {
* @param serviceUrl the odata service url to validate
* @param options options for the connection validation
* @param options.ignoreCertError ignore some certificate errors
* @param options.forceReValidation force re-validation of the url
* @param options.forceReValidation force re-validation of the url even if the same url has been prevously validated
* @param options.isSystem if true, the url will be treated as a system url rather than a service url
* @param options.odataVersion if specified will restrict catalog requests to only the specified odata version
* @returns true if the url is reachable, false if not, or an error message string
Expand Down Expand Up @@ -283,7 +292,7 @@ export class ConnectionValidator {
// More helpful context specific error
if (ErrorHandler.getErrorType(error) === ERROR_TYPE.CONNECTION) {
this.validity.reachable = false;
return errorHandler.logErrorMsgs(t('errors.serviceUrlNotFound', { url: serviceUrl }));
return errorHandler.logErrorMsgs(t('errors.systemOrserviceUrlNotFound', { url: serviceUrl }));
}

this.resetValidity();
Expand All @@ -301,9 +310,9 @@ export class ConnectionValidator {
*/
private getValidationResultFromStatusCode(status: string | number): boolean | string | IValidationLink {
if (status === 200) {
this.validity.reachable = true;
this.validity.authenticated = true;
this.validity.authRequired = false;
} else if (status === 404) {
} else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.NOT_FOUND) {
this.validity.reachable = false;
return ErrorHandler.getErrorMsgFromType(ERROR_TYPE.NOT_FOUND) ?? false;
} else if (ErrorHandler.isCertError(status)) {
Expand All @@ -313,10 +322,11 @@ export class ConnectionValidator {
} else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.AUTH) {
this.validity.reachable = true;
this.validity.authRequired = true;
this.validity.authenticated = false;
} else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.REDIRECT) {
this.validity.reachable = true;
return t('errors.urlRedirect');
} else if (status !== 404) {
} else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.CONNECTION) {
this.validity.reachable = false;
return ErrorHandler.getErrorMsgFromType(ERROR_TYPE.CONNECTION, `http code: ${status}`) ?? false;
}
Expand Down Expand Up @@ -348,6 +358,50 @@ export class ConnectionValidator {
return false;
}

/**
* Check whether basic auth is required for the given url, or for the previously validated url if none specified.
* This will also set the validity state for the url. This will not validate the URL.
*
* @param urlString - the url to validate, if not provided the previously validated url will be used
* @param client - optional, sap client code, if not provided the previously validated client will be used
* @param ignoreCertError
* @returns true if basic auth is required, false if not
*/
public async isAuthRequired(
urlString = this._validatedUrl,
client = this._validatedClient,
ignoreCertError = false
): Promise<boolean> {
if (!urlString) {
return false;
}

// Dont re-request if already validated
if (
this._validatedUrl === urlString &&
this._validatedClient === client &&
this.validity.authRequired !== undefined
) {
return this.validity.authRequired;
}
// New URL or client so we need to re-request
try {
const url = new URL(urlString);
if (client) {
url.searchParams.append(SAP_CLIENT_KEY, client);
}
this.validity.authRequired = this.validity.reachable =
ErrorHandler.getErrorType(
await this.checkSapService(url, undefined, undefined, { ignoreCertError })
) === ERROR_TYPE.AUTH;

return this.validity.authRequired;
} catch (error) {
errorHandler.logErrorMsgs(error);
return false; // Cannot determine if auth required
}
}

/**
* Test the connectivity with the specified service url using the provided credentials.
*
Expand Down Expand Up @@ -387,11 +441,18 @@ export class ConnectionValidator {
isSystem,
odataVersion
});

LoggerHelper.logger.debug(`ConnectionValidator.validateUrl() - status: ${status}; url: ${url}`);
// Since an exception was not thrown, this is a valid url
this.validity.urlFormat = true;
this._validatedUrl = url;
const valResult = this.getValidationResultFromStatusCode(status);

if (valResult === true && this.validity.authenticated === true) {
return true;
if (valResult === true) {
if (this.validity.authenticated === true) {
return true;
} else if (this.validity.authenticated === false) {
return t('errors.authenticationFailed');
}
}
return valResult;
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ export function getAbapOnPremSystemQuestions(
requiredOdataVersion?: OdataVersion
): Question<AbapOnPremAnswers>[] {
const connectValidator = connectionValidator ?? new ConnectionValidator();
let validClient = true;

const questions: Question<AbapOnPremAnswers>[] = [
{
Expand Down Expand Up @@ -229,10 +230,18 @@ export function getAbapOnPremSystemQuestions(
guiOptions: {
breadcrumb: t('prompts.sapClient.breadcrumb')
},
validate: validateClient
validate: (client) => {
const valRes = validateClient(client);
if (valRes === true) {
validClient = true;
return true;
}
validClient = false;
return valRes;
}
} as InputQuestion<AbapOnPremAnswers>,
{
when: () => (connectValidator.validity.reachable ? connectValidator.validity.authRequired === true : false),
when: () => connectValidator.isAuthRequired(),
type: 'input',
name: abapOnPremInternalPromptNames.systemUsername,
message: t('prompts.systemUsername.message'),
Expand All @@ -242,7 +251,7 @@ export function getAbapOnPremSystemQuestions(
validate: (user: string) => user?.length > 0
} as InputQuestion<AbapOnPremAnswers>,
{
when: () => (connectValidator.validity.reachable ? connectValidator.validity.authRequired === true : false),
when: () => connectValidator.isAuthRequired(),
type: 'password',
guiOptions: {
mandatory: true
Expand All @@ -252,7 +261,7 @@ export function getAbapOnPremSystemQuestions(
guiType: 'login',
mask: '*',
validate: async (password, { systemUrl, abapSystemUsername, sapClient }: AbapOnPremAnswers) => {
if (!(systemUrl && abapSystemUsername && password)) {
if (!(systemUrl && abapSystemUsername && password && validClient)) {
return false;
}
const valResult = await connectValidator.validateAuth(systemUrl, abapSystemUsername, password, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export function getNewSystemQuestions(promptOptions?: OdataServicePromptOptions)
*/
export function getUserSystemNameQuestion(): InputQuestion<NewSystemAnswers> {
let defaultSystemName: string;
let userModifiedSystemName: boolean = false;
const newSystemNamePrompt = {
type: 'input',
guiOptions: {
Expand All @@ -93,20 +94,22 @@ export function getUserSystemNameQuestion(): InputQuestion<NewSystemAnswers> {
name: promptNames.userSystemName,
message: t('prompts.systemName.message'),
default: async (answers: AbapOnPremAnswers & NewSystemAnswers) => {
if (answers.newSystemType === 'abapOnPrem' && answers.systemUrl) {
if (answers.newSystemType === 'abapOnPrem' && answers.systemUrl && !userModifiedSystemName) {
defaultSystemName = await suggestSystemName(answers.systemUrl, answers.sapClient);
return defaultSystemName;
}
return defaultSystemName;
return answers.userSystemName;
},
validate: async (systemName: string, answers: AbapOnPremAnswers & NewSystemAnswers) => {
let validationResult: boolean | string = false;
// Dont validate the suggested default system name
if (systemName === defaultSystemName) {
validationResult = true;
return true;
}
validationResult = await validateSystemName(systemName);
const validationResult = await validateSystemName(systemName);

if (validationResult === true) {
// Not the default system name, so the user modified
userModifiedSystemName = true;
const backendSystem = new BackendSystem({
name: systemName,
url: answers.systemUrl!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ async function isSystemNameInUse(systemName: string): Promise<boolean> {
*
* @param systemName a system name to validate
* @returns true if the name is valid, otherwise an error message
*/
export async function validateSystemName(systemName: string): Promise<boolean | string> {
*/ export async function validateSystemName(systemName: string): Promise<boolean | string> {
if (!systemName) {
return t('prompts.systemName.emptySystemNameWarning');
}
const systemExists = await isSystemNameInUse(systemName);
if (systemExists) {
return t('prompts.systemName.systemNameExistsWarning');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@
"message": "System name",
"hint": "Entering a system name will save the connection for re-use.",
"systemNameExistsWarning": "A system with that name already exists in the secure storage. Please try a different name.",
"reservedSystemNameWarning": "'{{ systemName }}' is a reserved system name. Please try a different name."
"reservedSystemNameWarning": "'{{ systemName }}' is a reserved system name. Please try a different name.",
"emptySystemNameWarning": "System name cannot be empty."
},
"systemSelection": {
"newSystemChoiceLabel": "New system"
Expand Down Expand Up @@ -129,7 +130,7 @@
"noSuchHostError": "No such host is known",
"odataServiceVersionMismatch": "The template you have chosen supports V{{requiredVersion}} OData services only. The provided version is V{{serviceVersion}}.",
"destinationAuthError": "The selected system is returning an authentication error. Please verify the destination configuration",
"serviceUrlNotFound": "Please verify the service url: {{- url}}, target system configuration and network connectivity",
"systemOrserviceUrlNotFound": "Please verify the url: {{- url}}, target system configuration and network connectivity",
"urlRedirect": "The service URL is redirecting",
"certValidationRequired": "Certificate validation is required to continue.",
"exitingGeneration": "Exiting generation. {{exitReason}}",
Expand Down
Loading

0 comments on commit 26ebcae

Please sign in to comment.