Skip to content

Commit

Permalink
Implemented regex validation
Browse files Browse the repository at this point in the history
  • Loading branch information
rmathis committed Aug 20, 2021
1 parent 347bbdf commit 8cacc88
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 79 deletions.
2 changes: 2 additions & 0 deletions backend/src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ message.delete-group-body=You are requesting to delete a group. If you complete
message.delete-attribute-title=Delete Attribute?
message.delete-attribute-body=You are requesting to delete a custom attribute. If you complete this process the attribute will be removed. This cannot be undone. Do you wish to continue?

message.group-pattern-fail=Pattern must match group url validation pattern.

message.must-be-unique=Must be unique.
message.must-be-number=Must be a number.
message.name-must-be-unique=Name must be unique.
Expand Down
2 changes: 0 additions & 2 deletions ui/src/app/admin/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export function useGroupUiSchema () {
}

export function useGroupUiValidator() {
console.log('hi')
return (formData, errors) => {
console.log(formData, errors)
if (!isNil(formData?.validationRegex)) {
const isValid = isValidRegex(formData.validationRegex);
if (!isValid) {
Expand Down
9 changes: 9 additions & 0 deletions ui/src/app/admin/hooks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useGroupUiValidator } from './hooks';

it('should validate against a regex', () => {
const validator = useGroupUiValidator();
const addErrorSpy = jest.fn();
const fail = validator({ validationRegex: '))(()' }, { validationRegex: { addError: addErrorSpy } });
expect(addErrorSpy).toHaveBeenCalled();
expect(validator({validationRegex: '/*'})).toBeUndefined();
});
37 changes: 33 additions & 4 deletions ui/src/app/core/user/UserContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,33 @@ const UserContext = React.createContext();
const { Provider, Consumer } = UserContext;

const path = '/admin/users/current';
const group = {
"name": "ADMIN-GROUP",
"resourceId": "admingroup",
"validationRegex": "^(R\\d*\\/)?P(?:\\d+(?:\\.\\d+)?Y)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?W)?(?:\\d+(?:\\.\\d+)?D)?(?:T(?:\\d+(?:\\.\\d+)?H)?(?:\\d+(?:\\.\\d+)?M)?(?:\\d+(?:\\.\\d+)?S)?)?$",
"ownerType": "GROUP",
"ownerId": "admingroup"
};

/*eslint-disable react-hooks/exhaustive-deps*/
function UserProvider({ children }) {

const { get, response } = useFetch(`${API_BASE_PATH}`, {
cacheLife: 10000,
cachePolicy: 'cache-first'
cachePolicy: 'no-cache'
});

React.useEffect(() => { loadUser() }, []);

async function loadUser() {
const user = await get(`${path}`);
if (response.ok) setUser(user);
if (response.ok) setUser({
...user,
group
});
}

const [user, setUser] = React.useState({});

return (
<Provider value={user}>{children}</Provider>
);
Expand All @@ -50,5 +60,24 @@ function useIsAdminOrInGroup() {
return isAdmin || isInGroup;
}

function useUserGroup() {
const user = useCurrentUser();
return user?.group;
}

function useUserGroupRegexValidator () {
const user = useCurrentUser();
return user?.group?.validationRegex;
}


export { UserContext, UserProvider, Consumer as UserConsumer, useCurrentUser, useIsAdmin, useIsAdminOrInGroup };
export {
UserContext,
UserProvider,
Consumer as UserConsumer,
useCurrentUser,
useIsAdmin,
useIsAdminOrInGroup,
useUserGroupRegexValidator,
useUserGroup
};
35 changes: 26 additions & 9 deletions ui/src/app/form/component/fields/FilterTargetField.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ const FilterTargetField = ({
onChange,
errorSchema,
formData,
formContext,
...props
}) => {

const { group } = formContext;
const regex = new RegExp(group?.validationRegex || '/*');

const typeFieldName = `${name}Type`;

const type = schema.properties[typeFieldName];
Expand All @@ -59,6 +64,8 @@ const FilterTargetField = ({
const [selectedTarget, setSelectedTarget] = React.useState([...(formData.value && !isNil(formData.value) && !isNil(formData.value[0]) ? formData.value : [])]);

const [term, setSearchTerm] = React.useState('');
const [match, setMatch] = React.useState(true);
const [touched, setTouched] = React.useState(false);
const [ids, setSearchIds] = React.useState([]);

const { get, response } = useFetch(`${API_BASE_PATH}/EntityIds/search`, {
Expand Down Expand Up @@ -113,6 +120,7 @@ const FilterTargetField = ({

const onEntityIdsChange = (value) => {
setSearchTerm(value);
setMatch(regex ? regex.test(value) : true);
};

const selectType = (option) => {
Expand Down Expand Up @@ -172,19 +180,28 @@ const FilterTargetField = ({
onInputChange={onEntityIdsChange}
selected={ [term] }
onChange={ () => {} }
onBlur={() => setTouched(true)}
onSearch={ (query) => setSearchTerm(query) }
renderMenuItemChildren={(option, { options, text }, index) => {
return <span className={options.indexOf(text) === index ? 'font-weight-bold' : ''}>{option}</span>;
}}>
renderMenuItemChildren={(option, { options, text }, index) =>
<span className={options.indexOf(text) === index ? 'font-weight-bold' : ''}>{option}</span>
}>
{({ isMenuShown, toggleMenu }) => (
<ToggleButton isOpen={isMenuShown} onClick={e => toggleMenu()} disabled={disabled || readonly} />
)}
</AsyncTypeahead>
<small>
<Translate value="message.entity-id-min-unique">
You must add at least one entity id target and they must each be unique.
</Translate>
</small>
{(!touched || match) ?
<small>
<Translate value="message.entity-id-min-unique">
You must add at least one entity id target and they must each be unique.
</Translate>
</small>
:
<small className="text-danger">
<Translate value="message.group-pattern-fail">
Invalid URL
</Translate>
</small>
}
</>
}
{ targetType === 'CONDITION_SCRIPT' &&
Expand Down Expand Up @@ -232,7 +249,7 @@ const FilterTargetField = ({
<div className="ml-2">
<Button variant="success"
type="button"
disabled={!term}
disabled={!term || !match}
onClick={() => onSelectValue(term)}>
<Translate value="action.add-entity-id">Add Entity ID</Translate>&nbsp;&nbsp;
<FontAwesomeIcon icon={faPlus} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
import { isValidRegex } from '../../../../core/utility/is_valid_regex';

export const BaseFilterDefinition = {
parser: (changes) => changes,
formatter: (changes) => changes,
display: (changes) => changes,
validator: (data = [], current = { resourceId: null }, group, targetProp, typeProp) => {

const filters = current ? data.filter(s => s.resourceId !== current.resourceId) : data;
const names = filters.map(s => s.name);

return (formData, errors) => {
if (names.indexOf(formData.name) > -1) {
errors.name.addError('message.name-unique');
}

if (formData.hasOwnProperty(targetProp)) {
if (formData[targetProp][typeProp] === 'REGEX') {
const { [targetProp]: { value } } = formData;
const isValid = isValidRegex(value[0]);
if (!isValid) {
errors[targetProp].value.addError('message.invalid-regex-pattern');
}
}

if (formData[targetProp][typeProp] === 'CONDITION_SCRIPT') {
const { [targetProp]: { value } } = formData;
if (!value[0]) {
errors[targetProp].value.addError('message.required-for-scripts');
}
}
}

return errors;
}
},
uiSchema: {
'@type': {
'ui:widget': 'hidden'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import API_BASE_PATH from "../../../../App.constant";
import {BaseFilterDefinition} from './BaseFilterDefinition';
import {removeNull} from '../../../../core/utility/remove_null';
import { isValidRegex } from '../../../../core/utility/is_valid_regex';
import defaultsDeep from "lodash/defaultsDeep";

export const EntityAttributesFilterWizard = {
Expand Down Expand Up @@ -36,32 +35,8 @@ export const EntityAttributesFilterWizard = {
}
}
}, BaseFilterDefinition.uiSchema),
validator: (data = [], current = { resourceId: null }) => {

const filters = current ? data.filter(s => s.resourceId !== current.resourceId) : data;
const names = filters.map(s => s.name);

return (formData, errors) => {
if (names.indexOf(formData.name) > -1) {
errors.name.addError('message.name-unique');
}

if (formData?.entityAttributesFilterTarget?.entityAttributesFilterTargetType === 'REGEX') {
const { entityAttributesFilterTarget: {value} } = formData;
const isValid = isValidRegex(value[0]);
if (!isValid) {
errors.entityAttributesFilterTarget.value.addError('message.invalid-regex-pattern');
}
}

if (formData?.entityAttributesFilterTarget?.entityAttributesFilterTargetType === 'CONDITION_SCRIPT') {
const { entityAttributesFilterTarget: { value } } = formData;
if (!value[0]) {
errors.entityAttributesFilterTarget.value.addError('message.required-for-scripts');
}
}
return errors;
}
validator: (data = [], current = { resourceId: null }, group) => {
return BaseFilterDefinition.validator(data, current, group, 'entityAttributesFilterTarget', 'entityAttributesFilterTargetType')
},
warnings: (data) => {
let warnings = {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import defaultsDeep from "lodash/defaultsDeep";
import API_BASE_PATH from "../../../../App.constant";
import { BaseFilterDefinition } from "./BaseFilterDefinition";

import { isValidRegex } from '../../../../core/utility/is_valid_regex';

export const NameIDFilterWizard = {
...BaseFilterDefinition,
uiSchema: defaultsDeep({
Expand All @@ -24,32 +22,8 @@ export const NameIDFilterWizard = {
type: 'NameIDFormat',
schema: `${API_BASE_PATH}/ui/NameIdFormatFilter`,
steps: [],
validator: (data = [], current = { resourceId: null }) => {

const filters = current ? data.filter(s => s.resourceId !== current.resourceId) : data;
const names = filters.map(s => s.name);

return (formData, errors) => {
if (names.indexOf(formData.name) > -1) {
errors.name.addError('message.name-unique');
}

if (formData?.nameIdFormatFilterTarget?.nameIdFormatFilterTargetType === 'REGEX') {
const { nameIdFormatFilterTarget: { value } } = formData;
const isValid = isValidRegex(value[0]);
if (!isValid) {
errors.nameIdFormatFilterTarget.value.addError('message.invalid-regex-pattern');
}
}

if (formData?.nameIdFormatFilterTarget?.nameIdFormatFilterTargetType === 'CONDITION_SCRIPT') {
const { nameIdFormatFilterTarget: { value } } = formData;
if (!value[0]) {
errors.nameIdFormatFilterTarget.value.addError('message.required-for-scripts');
}
}
return errors;
}
validator: (data = [], current = { resourceId: null }, group) => {
return BaseFilterDefinition.validator(data, current, group, 'nameIdFormatFilterTarget', 'nameIdFormatFilterTargetType')
},
formatter: (changes) => ({
...changes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { MetadataFilterTypes } from '../../filter';

export const BaseProviderDefinition = {
schemaPreprocessor: metadataFilterProcessor,
validator: (data = [], current = { resourceId: null }) => {
validator: (data = [], current = { resourceId: null }, group) => {
const providers = data.filter(p => p.resourceId !== current.resourceId);
const names = providers.map(s => s.name);
const ids = providers.map(s => s.xmlId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ export const DynamicHttpMetadataProviderWizard = {
label: 'DynamicHttpMetadataProvider',
type: 'DynamicHttpMetadataResolver',
schema: `${API_BASE_PATH}/ui/MetadataResolver/DynamicHttpMetadataResolver`,
validator: (data = [], current = { resourceId: null }) => {
const base = BaseProviderDefinition.validator(data, current);
validator: (data = [], current = { resourceId: null }, group) => {
const base = BaseProviderDefinition.validator(data, current, group);

const pattern = group?.validationRegex ? new RegExp(group?.validationRegex) : null;

return (formData, errors) => {
const errorList = base(formData, errors);
if (formData?.metadataRequestURLConstructionScheme['@type'] === 'Regex') {
Expand All @@ -21,6 +24,12 @@ export const DynamicHttpMetadataProviderWizard = {
}
}

if (formData?.metadataRequestURLConstructionScheme['@type'] === 'MetadataQueryProtocol') {
if (pattern && !pattern.test(formData?.metadataRequestURLConstructionScheme?.content)) {
errors?.metadataRequestURLConstructionScheme?.content?.addError('message.group-pattern-fail');
}
}

return errorList;
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ export const FileBackedHttpMetadataProviderWizard = {
label: 'FileBackedHttpMetadataProvider',
type: 'FileBackedHttpMetadataResolver',
schema: '/assets/schema/provider/filebacked-http.schema.json',
validator: (data = [], current = { resourceId: null }, group) => {
const base = BaseProviderDefinition.validator(data, current, group);

const pattern = group?.validationRegex ? new RegExp(group?.validationRegex) : null;

return (formData, errors) => {
const errorList = base(formData, errors);
if (formData?.metadataURL) {
if (pattern && !pattern.test(formData?.metadataURL)) {
errors?.metadataURL?.addError('message.group-pattern-fail');
}
}

return errorList;
}
},
steps: [
...BaseProviderDefinition.steps,
{
Expand Down
Loading

0 comments on commit 8cacc88

Please sign in to comment.