Skip to content

Commit

Permalink
Merge pull request wso2#5546 from Shalindri/userstore-field-validation
Browse files Browse the repository at this point in the history
  • Loading branch information
kayathiri4 authored Feb 16, 2024
2 parents cdc35a6 + 1882ae0 commit 7fe9c99
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 59 deletions.
7 changes: 7 additions & 0 deletions .changeset/afraid-shirts-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@wso2is/console": patch
"@wso2is/myaccount": patch
"@wso2is/i18n": patch
---

Add validation for user store input fields
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2023, WSO2 LLC. (https://www.wso2.com).
* Copyright (c) 2020-2024, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand All @@ -20,7 +20,7 @@ import { UserstoreConstants } from "@wso2is/core/constants";
import { IdentityAppsError } from "@wso2is/core/errors";
import { AlertInterface, AlertLevels, TestableComponentInterface } from "@wso2is/core/models";
import { addAlert } from "@wso2is/core/store";
import { Field, FormValue, Forms } from "@wso2is/forms";
import { Field, FormValue, Forms, Validation } from "@wso2is/forms";
import { ConfirmationModal, DangerZone, EmphasizedSegment, LinkButton, PrimaryButton } from "@wso2is/react-components";
import { DangerZoneGroup } from "@wso2is/react-components/src";
import React, { FunctionComponent, ReactElement, useEffect, useState } from "react";
Expand All @@ -32,8 +32,10 @@ import { SqlEditor } from "..";
import { userstoresConfig } from "../../../../extensions";
import { AppConstants, history } from "../../../core";
import { deleteUserStore, patchUserStore } from "../../api";
import { CONSUMER_USERSTORE, CONSUMER_USERSTORE_ID, DISABLED } from "../../constants";
import { CONSUMER_USERSTORE, CONSUMER_USERSTORE_ID, DISABLED, USERSTORE_VALIDATION_REGEX_PATTERNS }
from "../../constants";
import { PatchData, PropertyAttribute, RequiredBinary, TypeProperty, UserStore } from "../../models";
import { validateInputWithRegex } from "../../utils/userstore-utils";

/**
* Prop types of `EditBasicDetailsUserStore` component
Expand Down Expand Up @@ -414,6 +416,26 @@ export const EditBasicDetailsUserStore: FunctionComponent<EditBasicDetailsUserSt
"description.placeholder") }
value={ userStore?.description }
data-testid={ `${ testId }-form-description-textarea` }
validation={ async (value: string, validation: Validation) => {
const validityResult: Map<string, string | boolean> =
validateInputWithRegex(value, USERSTORE_VALIDATION_REGEX_PATTERNS
.xssEscapeRegEx);
const isMatch: string = validityResult.get("isMatch").toString();

if (isMatch === "true") {
validation.isValid = false;
const invalidString: string = validityResult
.get("invalidStringValue").toString();

validation.errorMessages.push(
t("console:manage.features.userstores.forms.general.description"
+ ".validationErrorMessages.invalidInputErrorMessage", {
invalidString: invalidString
})
);
}
}
}
/>
}
</Grid.Column>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import { useDispatch } from "react-redux";
import { Dispatch } from "redux";
import { Button, Divider, Grid, Header, Icon } from "semantic-ui-react";
import { getUserStores, testConnection } from "../../api";
import { DISABLED, JDBC, USERSTORE_NAME_CHARACTER_LIMIT } from "../../constants";
import { DISABLED, JDBC, USERSTORE_NAME_CHARACTER_LIMIT, USERSTORE_VALIDATION_REGEX_PATTERNS } from "../../constants";
import { PropertyAttribute, TestConnection, TypeProperty, UserStoreListItem, UserstoreType } from "../../models";
import { validateInputWithRegex } from "../../utils/userstore-utils";

/**
* Prop types of the `GeneralDetails` component
Expand Down Expand Up @@ -170,7 +171,7 @@ export const GeneralDetailsUserstore: FunctionComponent<GeneralDetailsUserstoreP
placeholder={ t("console:manage.features.userstores.forms.general.name.placeholder") }
value={ values?.get("name")?.toString() }
data-testid={ `${ testId }-form-name-input` }
validation={ async (value: FormValue, validation: Validation) => {
validation={ async (value: string, validation: Validation) => {
let userStores: UserStoreListItem[] = null;

try {
Expand Down Expand Up @@ -207,6 +208,22 @@ export const GeneralDetailsUserstore: FunctionComponent<GeneralDetailsUserstoreP
})
);
}

const validityResult: Map<string, string | boolean> = validateInputWithRegex(value,
USERSTORE_VALIDATION_REGEX_PATTERNS.xssEscapeRegEx);
const isMatch: string = validityResult.get("isMatch").toString();

if (isMatch === "true") {
validation.isValid = false;
const invalidString: string = validityResult.get("invalidStringValue").toString();

validation.errorMessages.push(
t("console:manage.features.userstores.forms.general.name"
+ ".validationErrorMessages.invalidInputErrorMessage", {
invalidString: invalidString
})
);
}
}
}
/>
Expand All @@ -220,6 +237,24 @@ export const GeneralDetailsUserstore: FunctionComponent<GeneralDetailsUserstoreP
"description.placeholder") }
value={ values?.get("description")?.toString() }
data-testid={ `${ testId }-form-description-textarea` }
validation={ async (value: string, validation: Validation) => {
const validityResult: Map<string, string | boolean> = validateInputWithRegex(value,
USERSTORE_VALIDATION_REGEX_PATTERNS.xssEscapeRegEx);
const isMatch: string = validityResult.get("isMatch").toString();

if (isMatch === "true") {
validation.isValid = false;
const invalidString: string = validityResult.get("invalidStringValue").toString();

validation.errorMessages.push(
t("console:manage.features.userstores.forms.general.description"
+ ".validationErrorMessages.invalidInputErrorMessage", {
invalidString: invalidString
})
);
}
}
}
/>
{
basicProperties?.map(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com).
* Copyright (c) 2020-2024, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -163,6 +163,17 @@ export const USERSTORE_REGEX_PROPERTIES: UserStoreRegexPropertiesInterface = {
UsernameRegEx: "UsernameJavaScriptRegEx"
};

interface UserStoreValidationRegexPatternInterface {
xssEscapeRegEx: string;
}

/**
* User store validation regEx patterns
*/
export const USERSTORE_VALIDATION_REGEX_PATTERNS: UserStoreValidationRegexPatternInterface = {
xssEscapeRegEx: "\\$\\{[^}]*\\}"
};

/**
* Disabled property name.
*/
Expand Down
124 changes: 77 additions & 47 deletions apps/console/src/features/userstores/utils/userstore-utils.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
/**
* Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). All Rights Reserved.
* Copyright (c) 2020-2024, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the 'License'); you may not use this file except
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import {
CategorizedProperties,
TypeProperty,
UserStoreProperty,
UserstorePropertiesCategories,
UserstoreType
CategorizedProperties, PropertyAttribute, TypeProperty, UserStoreProperty,
UserstorePropertiesCategories, UserstoreType
} from "../models";

export const reOrganizeProperties = (
properties: UserstoreType["properties"],
valueProperties?: UserStoreProperty[]
): CategorizedProperties => {
const flattenedProperties: TypeProperty[] = [];

flattenedProperties.push(...properties.Advanced);
flattenedProperties.push(...properties.Mandatory);
flattenedProperties.push(...properties.Optional);
Expand Down Expand Up @@ -62,69 +60,77 @@ export const reOrganizeProperties = (
const basicOptionalSqlSelectProperties: TypeProperty[] = [];

flattenedProperties.forEach((property: TypeProperty) => {
const category = property.attributes?.find((attribute) => attribute.name === "category")?.value;
const required = property.attributes?.find((attribute) => attribute.name === "required")?.value === "true";
const sql = property.attributes?.find((attribute) => attribute.name === "type")?.value === "sql";
const INSERT = property.description.toLowerCase().includes("add");
const DELETE = property.description.toLowerCase().includes("delete");
const UPDATE = property.description.toLowerCase().includes("update");
const category: string = property.attributes?.find((attribute: PropertyAttribute) =>
attribute.name === "category")?.value;
const required: boolean = property.attributes?.find((attribute: PropertyAttribute) =>
attribute.name === "required")?.value === "true";
const sql: boolean = property.attributes?.find((attribute: PropertyAttribute) =>
attribute.name === "type")?.value === "sql";
const INSERT: boolean = property.description.toLowerCase().includes("add");
const DELETE: boolean = property.description.toLowerCase().includes("delete");
const UPDATE: boolean = property.description.toLowerCase().includes("update");

if (valueProperties) {
property.value = valueProperties.find((valueProperty) => valueProperty.name === property.name)?.value ?? "";
property.value = valueProperties.find((valueProperty: UserStoreProperty) =>
valueProperty.name === property.name)?.value ?? "";
}

switch (category) {
case UserstorePropertiesCategories.CONNECTION:
required
? connectionRequiredProperties.push(property)
: sql
? INSERT
? connectionOptionalSqlInsertProperties.push(property)
: DELETE
? connectionOptionalSqlDeleteProperties.push(property)
: UPDATE
? connectionOptionalSqlUpdateProperties.push(property)
: connectionOptionalSqlSelectProperties.push(property)
: connectionOptionalProperties.push(property);
? INSERT
? connectionOptionalSqlInsertProperties.push(property)
: DELETE
? connectionOptionalSqlDeleteProperties.push(property)
: UPDATE
? connectionOptionalSqlUpdateProperties.push(property)
: connectionOptionalSqlSelectProperties.push(property)
: connectionOptionalProperties.push(property);

break;
case UserstorePropertiesCategories.GROUP:
required
? groupRequiredProperties.push(property)
: sql
? INSERT
? groupOptionalSqlInsertProperties.push(property)
: DELETE
? groupOptionalSqlDeleteProperties.push(property)
: UPDATE
? groupOptionalSqlUpdateProperties.push(property)
: groupOptionalSqlSelectProperties.push(property)
: groupOptionalProperties.push(property);
? INSERT
? groupOptionalSqlInsertProperties.push(property)
: DELETE
? groupOptionalSqlDeleteProperties.push(property)
: UPDATE
? groupOptionalSqlUpdateProperties.push(property)
: groupOptionalSqlSelectProperties.push(property)
: groupOptionalProperties.push(property);

break;
case UserstorePropertiesCategories.USER:
required
? userRequiredProperties.push(property)
: sql
? INSERT
? userOptionalSqlInsertProperties.push(property)
: DELETE
? userOptionalSqlDeleteProperties.push(property)
: UPDATE
? userOptionalSqlUpdateProperties.push(property)
: userOptionalSqlSelectProperties.push(property)
: userOptionalProperties.push(property);
? INSERT
? userOptionalSqlInsertProperties.push(property)
: DELETE
? userOptionalSqlDeleteProperties.push(property)
: UPDATE
? userOptionalSqlUpdateProperties.push(property)
: userOptionalSqlSelectProperties.push(property)
: userOptionalProperties.push(property);

break;
case UserstorePropertiesCategories.BASIC:
required
? basicRequiredProperties.push(property)
: sql
? INSERT
? basicOptionalSqlInsertProperties.push(property)
: DELETE
? basicOptionalSqlDeleteProperties.push(property)
: UPDATE
? basicOptionalSqlUpdateProperties.push(property)
: basicOptionalSqlSelectProperties.push(property)
: basicOptionalProperties.push(property);
? INSERT
? basicOptionalSqlInsertProperties.push(property)
: DELETE
? basicOptionalSqlDeleteProperties.push(property)
: UPDATE
? basicOptionalSqlUpdateProperties.push(property)
: basicOptionalSqlSelectProperties.push(property)
: basicOptionalProperties.push(property);

break;
}
});
Expand Down Expand Up @@ -180,3 +186,27 @@ export const reOrganizeProperties = (
}
};
};

/**
* This validates and extracts the matched string with Regex.
*
* @returns validity status and the invalid string
*/
export const validateInputWithRegex = (input: string, regex: string): Map<string, string | boolean> => {
const regExpInvalidSymbols: RegExp = new RegExp(regex);

let isMatch: boolean = false;
let invalidStringValue: string = null;

if (regExpInvalidSymbols.test(input)) {
isMatch = true;
invalidStringValue = regExpInvalidSymbols.exec(input).toString();
}

const validityResultsMap: Map<string, string | boolean> = new Map<string, string | boolean>();

validityResultsMap.set("isMatch", isMatch);
validityResultsMap.set("invalidStringValue", invalidStringValue);

return validityResultsMap;
};
8 changes: 6 additions & 2 deletions modules/i18n/src/translations/en-US/portals/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12660,15 +12660,19 @@ export const console: ConsoleNS = {
general: {
description: {
label: "Description",
placeholder: "Enter a description"
placeholder: "Enter a description",
validationErrorMessages: {
invalidInputErrorMessage: "Description cannot contain the pattern {{invalidString}}."
}
},
name: {
label: "Name",
placeholder: "Enter a name",
requiredErrorMessage: "Name is a required field",
validationErrorMessages: {
alreadyExistsErrorMessage: "A user store with this name already exists.",
maxCharLimitErrorMessage: "User store name cannot exceed {{maxLength}} characters."
maxCharLimitErrorMessage: "User store name cannot exceed {{maxLength}} characters.",
invalidInputErrorMessage: "User store name cannot contain the pattern {{invalidString}}."
}
},
type: {
Expand Down
11 changes: 9 additions & 2 deletions modules/i18n/src/translations/fr-FR/portals/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10992,12 +10992,19 @@ export const console: ConsoleNS = {
general: {
description: {
label: "Description",
placeholder: "Veuillez saisir une description"
placeholder: "Veuillez saisir une description",
validationErrorMessages: {
invalidInputErrorMessage: "La description ne peut pas contenir le modèle {{invalidString}}."
}
},
name: {
label: "Nom",
placeholder: "Veuillez saisir un nom",
requiredErrorMessage: "Le nom de l'annuaire est obligatoire"
requiredErrorMessage: "Le nom de l'annuaire est obligatoire",
validationErrorMessages: {
invalidInputErrorMessage: "Le nom du magasin d'utilisateurs ne peut pas contenir"
+ "le modèle {{invalidString}}."
}
},
type: {
label: "Type",
Expand Down
Loading

0 comments on commit 7fe9c99

Please sign in to comment.