This is an automated email from the ASF dual-hosted git repository. marat pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit 1f8a5c3435b8dbf04b65b76bc4ca42c95f436492 Author: Marat Gubaidullin <[email protected]> AuthorDate: Tue Nov 26 19:13:06 2024 -0500 Sensitive fields validator in Beans --- .../designer/property/property/BeanProperties.tsx | 75 ++++++++++------------ .../property/property/ComponentPropertyField.tsx | 12 +--- .../property/property/KameletPropertyField.tsx | 10 +-- .../webui/src/designer/utils/ValidatorUtils.ts | 29 +++++++++ karavan-app/src/main/webui/src/util/StringUtils.ts | 17 +++++ karavan-designer/public/example/demo.camel.yaml | 27 +++----- .../designer/property/property/BeanProperties.tsx | 75 ++++++++++------------ .../property/property/ComponentPropertyField.tsx | 12 +--- .../property/property/KameletPropertyField.tsx | 10 +-- .../src/designer/utils/ValidatorUtils.ts | 29 +++++++++ .../designer/property/property/BeanProperties.tsx | 75 ++++++++++------------ .../property/property/ComponentPropertyField.tsx | 12 +--- .../property/property/KameletPropertyField.tsx | 10 +-- karavan-space/src/designer/utils/ValidatorUtils.ts | 29 +++++++++ 14 files changed, 223 insertions(+), 199 deletions(-) diff --git a/karavan-app/src/main/webui/src/designer/property/property/BeanProperties.tsx b/karavan-app/src/main/webui/src/designer/property/property/BeanProperties.tsx index 34c93cca..d49cd985 100644 --- a/karavan-app/src/main/webui/src/designer/property/property/BeanProperties.tsx +++ b/karavan-app/src/main/webui/src/designer/property/property/BeanProperties.tsx @@ -16,13 +16,18 @@ */ import React, {useEffect, useState} from 'react'; import { - TextInput, Button, Tooltip, Popover, InputGroup, InputGroupItem, capitalize, + Button, + capitalize, + InputGroup, + InputGroupItem, + Popover, + TextInput, + Tooltip, + ValidatedOptions, } from '@patternfly/react-core'; import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; -import { - BeanFactoryDefinition, -} from "karavan-core/lib/model/CamelDefinition"; +import {BeanFactoryDefinition,} from "karavan-core/lib/model/CamelDefinition"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {SensitiveKeys} from "karavan-core/lib/model/CamelMetadata"; import {v4 as uuidv4} from "uuid"; @@ -31,13 +36,11 @@ import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; import {useDesignerStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; import {KubernetesIcon} from "../../icons/ComponentIcons"; - +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; interface Props { type: 'constructors' | 'properties' @@ -51,26 +54,26 @@ export function BeanProperties (props: Props) { const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [infrastructureSelectorUuid, setInfrastructureSelectorUuid] = useState<string | undefined>(undefined); - const [properties, setProperties] = useState<Map<string, [string, string, boolean]>>(new Map<string, [string, string, boolean]>()); - const [constructors, setConstructors] = useState<Map<string, [number, string, boolean]>>(new Map<string, [number, string, boolean]>()); + const [properties, setProperties] = useState<Map<string, [string, string]>>(new Map<string, [string, string]>()); + const [constructors, setConstructors] = useState<Map<string, [number, string]>>(new Map<string, [number, string]>()); useEffect(()=> { setProperties(preparePropertiesMap((selectedStep as BeanFactoryDefinition)?.properties)) setConstructors(prepareConstructorsMap((selectedStep as BeanFactoryDefinition)?.constructors)) }, [selectedStep?.uuid]) - function preparePropertiesMap (properties: any): Map<string, [string, string, boolean]> { - const result = new Map<string, [string, string, boolean]>(); + function preparePropertiesMap (properties: any): Map<string, [string, string]> { + const result = new Map<string, [string, string]>(); if (properties) { - Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k], false])); + Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k]])); } return result; } - function prepareConstructorsMap (constructors: any): Map<string, [number, string, boolean]> { - const result = new Map<string, [number, string, boolean]>(); + function prepareConstructorsMap (constructors: any): Map<string, [number, string]> { + const result = new Map<string, [number, string]>(); if (constructors) { - Object.keys(constructors).forEach((k, i, a) => result.set(uuidv4(), [parseInt(k), constructors[k], false])); + Object.keys(constructors).forEach((k, i, a) => result.set(uuidv4(), [parseInt(k), constructors[k]])); } return result; } @@ -103,17 +106,17 @@ export function BeanProperties (props: Props) { } } - function propertyChanged (uuid: string, key: string, value: string, showPassword: boolean) { + function propertyChanged (uuid: string, key: string, value: string) { setProperties(prevState => { - prevState.set(uuid, [key, value, showPassword]); + prevState.set(uuid, [key, value]); return prevState; }); onBeanPropertyUpdate(); } - function constructorChanged (uuid: string, key: number, value: string, showPassword: boolean) { + function constructorChanged (uuid: string, key: number, value: string) { setConstructors(prevState => { - prevState.set(uuid, [key, value, showPassword]); + prevState.set(uuid, [key, value]); return prevState; }); onBeanConstructorsUpdate(); @@ -140,7 +143,7 @@ export function BeanProperties (props: Props) { const uuid = infrastructureSelectorUuid; if (propertyId && uuid){ if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}"; - propertyChanged(uuid, propertyId, value, false); + propertyChanged(uuid, propertyId, value); setInfrastructureSelector(false); setInfrastructureSelectorProperty(undefined); } @@ -201,20 +204,19 @@ export function BeanProperties (props: Props) { const i = v[0]; const key = v[1][0]; const value = v[1][1]; - const showPassword = v[1][2]; const isSecret = false; return ( <div key={"key-" + i} className="bean-property"> <TextInput placeholder="Argument Index" className="text-field" isRequired type="text" id={"key-" + i} name={"key-" + i} value={key} onChange={(_, beanFieldName) => { - constructorChanged(i, parseInt(beanFieldName) , value, showPassword) + constructorChanged(i, parseInt(beanFieldName) , value) }}/> <InputGroup> <InputGroupItem isFill> <TextInput placeholder="Argument Value" - type={isSecret && !showPassword ? "password" : "text"} + type='text' autoComplete="off" className="text-field" isRequired @@ -222,14 +224,9 @@ export function BeanProperties (props: Props) { name={"value-" + i} value={value} onChange={(_, value) => { - constructorChanged(i, key, value, showPassword) + constructorChanged(i, key, value) }}/> </InputGroupItem> - {isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => constructorChanged(i, key, value, !showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip>} </InputGroup> <Button variant="link" className="delete-button" onClick={e => constructorDeleted(i)}> <DeleteIcon/> @@ -237,7 +234,7 @@ export function BeanProperties (props: Props) { </div> ) })} - <Button variant="link" className="add-button" onClick={e => constructorChanged(uuidv4(), constructors.size, '', false)}> + <Button variant="link" className="add-button" onClick={e => constructorChanged(uuidv4(), constructors.size, '')}> <AddIcon/>Add argument</Button> </> ) @@ -250,8 +247,8 @@ export function BeanProperties (props: Props) { const i = v[0]; const key = v[1][0]; const value = v[1][1]; - const showPassword = v[1][2]; const isSecret = key !== undefined && SensitiveKeys.includes(key.toLowerCase()); + const validated = (isSecret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const inInfrastructure = InfrastructureAPI.infrastructure !== 'local'; const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? KubernetesIcon("infra-button"): <DockerIcon/> return ( @@ -259,11 +256,11 @@ export function BeanProperties (props: Props) { <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id={"key-" + i} name={"key-" + i} value={key} onChange={(_, beanFieldName) => { - propertyChanged(i, beanFieldName, value, showPassword) + propertyChanged(i, beanFieldName, value) }}/> <InputGroup> {inInfrastructure && - <Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}> + <Tooltip position="bottom-end" content={'Select from ' + capitalize(InfrastructureAPI.infrastructure)}> <Button variant="control" onClick={e => openInfrastructureSelector(i, key)}> {icon} </Button> @@ -271,28 +268,24 @@ export function BeanProperties (props: Props) { <InputGroupItem isFill> <TextInput placeholder="Bean Field Value" - type={isSecret && !showPassword ? "password" : "text"} + type='text' autoComplete="off" className="text-field" isRequired + validated={validated} id={"value-" + i} name={"value-" + i} value={value} onChange={(_, value) => { - propertyChanged(i, key, value, showPassword) + propertyChanged(i, key, value) }}/> </InputGroupItem> - {isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => propertyChanged(i, key, value, !showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip>} </InputGroup> <Button variant="link" className="delete-button" onClick={e => propertyDeleted(i)}><DeleteIcon/></Button> </div> ) })} - <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '', false)}> + <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '')}> <AddIcon/>Add property</Button> </> ) diff --git a/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx b/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx index 27e22780..57ff7d4c 100644 --- a/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx +++ b/karavan-app/src/main/webui/src/designer/property/property/ComponentPropertyField.tsx @@ -78,7 +78,6 @@ export function ComponentPropertyField(props: Props) { const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); const [showEditor, setShowEditor] = useState<boolean>(false); - const [showPassword, setShowPassword] = useState<boolean>(false); const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [id, setId] = useState<string>(prefix + "-" + props.property.name); @@ -266,7 +265,7 @@ export function ComponentPropertyField(props: Props) { </Tooltip>} {(!showEditor || property.secret) && <TextInput className="text-field" isRequired ref={ref} - type={property.secret && !showPassword ? "password" : "text"} + type="text" autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -299,13 +298,6 @@ export function ComponentPropertyField(props: Props) { setCheckChanges(false); }}/> </InputGroupItem>} - {property.secret && - <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => setShowPassword(!showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip> - } <InputGroupItem> <PropertyPlaceholderDropdown property={property} value={value} onComponentPropertyChange={(parameter, v) => { onParametersChange(parameter, v); @@ -322,7 +314,7 @@ export function ComponentPropertyField(props: Props) { <InputGroupItem isFill> <TextInput className="text-field" isRequired - type={(property.secret ? "password" : "text")} + type="text" autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} diff --git a/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx b/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx index 0e1e2bf0..d70582b5 100644 --- a/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx +++ b/karavan-app/src/main/webui/src/designer/property/property/KameletPropertyField.tsx @@ -51,7 +51,6 @@ export function KameletPropertyField(props: Props) { const [dark] = useDesignerStore((s) => [s.dark], shallow) const [showEditor, setShowEditor] = useState<boolean>(false); - const [showPassword, setShowPassword] = useState<boolean>(false); const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); @@ -171,7 +170,7 @@ export function KameletPropertyField(props: Props) { <TextInput ref={ref} className="text-field" isRequired - type={property.format && !showPassword ? "password" : "text"} + type='text' autoComplete="off" id={id} name={id} value={textValue} @@ -220,13 +219,6 @@ export function KameletPropertyField(props: Props) { setCheckChanges(true); }}/> </InputGroupItem> - {property.format === "password" && - <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => setShowPassword(!showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip> - } </InputGroup> } diff --git a/karavan-app/src/main/webui/src/designer/utils/ValidatorUtils.ts b/karavan-app/src/main/webui/src/designer/utils/ValidatorUtils.ts new file mode 100644 index 00000000..b9240dad --- /dev/null +++ b/karavan-app/src/main/webui/src/designer/utils/ValidatorUtils.ts @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, 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 KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function isSensitiveFieldValid(field: string): boolean { + if (field === undefined || field.trim() === "") { + return true; + } + if (field.startsWith("{{") && field.endsWith("}}")) { + const content = field.slice(2, -2).trim(); + return content !== ""; + } + return false; +} + + diff --git a/karavan-app/src/main/webui/src/util/StringUtils.ts b/karavan-app/src/main/webui/src/util/StringUtils.ts index e6ad8644..e1e9e56c 100644 --- a/karavan-app/src/main/webui/src/util/StringUtils.ts +++ b/karavan-app/src/main/webui/src/util/StringUtils.ts @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, 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 KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + export function isEmpty(str: string) { return !str?.trim(); } diff --git a/karavan-designer/public/example/demo.camel.yaml b/karavan-designer/public/example/demo.camel.yaml index b6f75ab3..b60f1af7 100644 --- a/karavan-designer/public/example/demo.camel.yaml +++ b/karavan-designer/public/example/demo.camel.yaml @@ -1,7 +1,3 @@ -- rest: - id: rest-fe3c - get: - - id: get-8c13 - routeTemplate: id: routeFileReaderTemplate description: File reader @@ -9,18 +5,12 @@ id: routeFileReader description: File reader from: - id: from-c667 - description: Read file - uri: file - parameters: - directoryName: "{{folderName}}" - noop: true + id: from-4101 + uri: sftp steps: - to: id: to-1234 - uri: direct - parameters: - name: converter + uri: sql parameters: - name: folderName - routeTemplate: @@ -30,12 +20,8 @@ id: routeFileReader 2 description: File reader 2 from: - id: from-c667 - description: Read file - uri: file - parameters: - directoryName: "{{folderName}}" - noop: true + id: from-4101 + uri: sftp steps: - to: id: to-1234 @@ -49,6 +35,9 @@ from: id: from-b6a5 uri: kamelet:aws-kinesis-source + parameters: + secretKey: asasdasd + accessKey: asdasd steps: - log: id: log-b47b diff --git a/karavan-designer/src/designer/property/property/BeanProperties.tsx b/karavan-designer/src/designer/property/property/BeanProperties.tsx index 34c93cca..d49cd985 100644 --- a/karavan-designer/src/designer/property/property/BeanProperties.tsx +++ b/karavan-designer/src/designer/property/property/BeanProperties.tsx @@ -16,13 +16,18 @@ */ import React, {useEffect, useState} from 'react'; import { - TextInput, Button, Tooltip, Popover, InputGroup, InputGroupItem, capitalize, + Button, + capitalize, + InputGroup, + InputGroupItem, + Popover, + TextInput, + Tooltip, + ValidatedOptions, } from '@patternfly/react-core'; import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; -import { - BeanFactoryDefinition, -} from "karavan-core/lib/model/CamelDefinition"; +import {BeanFactoryDefinition,} from "karavan-core/lib/model/CamelDefinition"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {SensitiveKeys} from "karavan-core/lib/model/CamelMetadata"; import {v4 as uuidv4} from "uuid"; @@ -31,13 +36,11 @@ import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; import {useDesignerStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; import {KubernetesIcon} from "../../icons/ComponentIcons"; - +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; interface Props { type: 'constructors' | 'properties' @@ -51,26 +54,26 @@ export function BeanProperties (props: Props) { const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [infrastructureSelectorUuid, setInfrastructureSelectorUuid] = useState<string | undefined>(undefined); - const [properties, setProperties] = useState<Map<string, [string, string, boolean]>>(new Map<string, [string, string, boolean]>()); - const [constructors, setConstructors] = useState<Map<string, [number, string, boolean]>>(new Map<string, [number, string, boolean]>()); + const [properties, setProperties] = useState<Map<string, [string, string]>>(new Map<string, [string, string]>()); + const [constructors, setConstructors] = useState<Map<string, [number, string]>>(new Map<string, [number, string]>()); useEffect(()=> { setProperties(preparePropertiesMap((selectedStep as BeanFactoryDefinition)?.properties)) setConstructors(prepareConstructorsMap((selectedStep as BeanFactoryDefinition)?.constructors)) }, [selectedStep?.uuid]) - function preparePropertiesMap (properties: any): Map<string, [string, string, boolean]> { - const result = new Map<string, [string, string, boolean]>(); + function preparePropertiesMap (properties: any): Map<string, [string, string]> { + const result = new Map<string, [string, string]>(); if (properties) { - Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k], false])); + Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k]])); } return result; } - function prepareConstructorsMap (constructors: any): Map<string, [number, string, boolean]> { - const result = new Map<string, [number, string, boolean]>(); + function prepareConstructorsMap (constructors: any): Map<string, [number, string]> { + const result = new Map<string, [number, string]>(); if (constructors) { - Object.keys(constructors).forEach((k, i, a) => result.set(uuidv4(), [parseInt(k), constructors[k], false])); + Object.keys(constructors).forEach((k, i, a) => result.set(uuidv4(), [parseInt(k), constructors[k]])); } return result; } @@ -103,17 +106,17 @@ export function BeanProperties (props: Props) { } } - function propertyChanged (uuid: string, key: string, value: string, showPassword: boolean) { + function propertyChanged (uuid: string, key: string, value: string) { setProperties(prevState => { - prevState.set(uuid, [key, value, showPassword]); + prevState.set(uuid, [key, value]); return prevState; }); onBeanPropertyUpdate(); } - function constructorChanged (uuid: string, key: number, value: string, showPassword: boolean) { + function constructorChanged (uuid: string, key: number, value: string) { setConstructors(prevState => { - prevState.set(uuid, [key, value, showPassword]); + prevState.set(uuid, [key, value]); return prevState; }); onBeanConstructorsUpdate(); @@ -140,7 +143,7 @@ export function BeanProperties (props: Props) { const uuid = infrastructureSelectorUuid; if (propertyId && uuid){ if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}"; - propertyChanged(uuid, propertyId, value, false); + propertyChanged(uuid, propertyId, value); setInfrastructureSelector(false); setInfrastructureSelectorProperty(undefined); } @@ -201,20 +204,19 @@ export function BeanProperties (props: Props) { const i = v[0]; const key = v[1][0]; const value = v[1][1]; - const showPassword = v[1][2]; const isSecret = false; return ( <div key={"key-" + i} className="bean-property"> <TextInput placeholder="Argument Index" className="text-field" isRequired type="text" id={"key-" + i} name={"key-" + i} value={key} onChange={(_, beanFieldName) => { - constructorChanged(i, parseInt(beanFieldName) , value, showPassword) + constructorChanged(i, parseInt(beanFieldName) , value) }}/> <InputGroup> <InputGroupItem isFill> <TextInput placeholder="Argument Value" - type={isSecret && !showPassword ? "password" : "text"} + type='text' autoComplete="off" className="text-field" isRequired @@ -222,14 +224,9 @@ export function BeanProperties (props: Props) { name={"value-" + i} value={value} onChange={(_, value) => { - constructorChanged(i, key, value, showPassword) + constructorChanged(i, key, value) }}/> </InputGroupItem> - {isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => constructorChanged(i, key, value, !showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip>} </InputGroup> <Button variant="link" className="delete-button" onClick={e => constructorDeleted(i)}> <DeleteIcon/> @@ -237,7 +234,7 @@ export function BeanProperties (props: Props) { </div> ) })} - <Button variant="link" className="add-button" onClick={e => constructorChanged(uuidv4(), constructors.size, '', false)}> + <Button variant="link" className="add-button" onClick={e => constructorChanged(uuidv4(), constructors.size, '')}> <AddIcon/>Add argument</Button> </> ) @@ -250,8 +247,8 @@ export function BeanProperties (props: Props) { const i = v[0]; const key = v[1][0]; const value = v[1][1]; - const showPassword = v[1][2]; const isSecret = key !== undefined && SensitiveKeys.includes(key.toLowerCase()); + const validated = (isSecret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const inInfrastructure = InfrastructureAPI.infrastructure !== 'local'; const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? KubernetesIcon("infra-button"): <DockerIcon/> return ( @@ -259,11 +256,11 @@ export function BeanProperties (props: Props) { <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id={"key-" + i} name={"key-" + i} value={key} onChange={(_, beanFieldName) => { - propertyChanged(i, beanFieldName, value, showPassword) + propertyChanged(i, beanFieldName, value) }}/> <InputGroup> {inInfrastructure && - <Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}> + <Tooltip position="bottom-end" content={'Select from ' + capitalize(InfrastructureAPI.infrastructure)}> <Button variant="control" onClick={e => openInfrastructureSelector(i, key)}> {icon} </Button> @@ -271,28 +268,24 @@ export function BeanProperties (props: Props) { <InputGroupItem isFill> <TextInput placeholder="Bean Field Value" - type={isSecret && !showPassword ? "password" : "text"} + type='text' autoComplete="off" className="text-field" isRequired + validated={validated} id={"value-" + i} name={"value-" + i} value={value} onChange={(_, value) => { - propertyChanged(i, key, value, showPassword) + propertyChanged(i, key, value) }}/> </InputGroupItem> - {isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => propertyChanged(i, key, value, !showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip>} </InputGroup> <Button variant="link" className="delete-button" onClick={e => propertyDeleted(i)}><DeleteIcon/></Button> </div> ) })} - <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '', false)}> + <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '')}> <AddIcon/>Add property</Button> </> ) diff --git a/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx b/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx index 27e22780..57ff7d4c 100644 --- a/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx +++ b/karavan-designer/src/designer/property/property/ComponentPropertyField.tsx @@ -78,7 +78,6 @@ export function ComponentPropertyField(props: Props) { const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); const [showEditor, setShowEditor] = useState<boolean>(false); - const [showPassword, setShowPassword] = useState<boolean>(false); const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [id, setId] = useState<string>(prefix + "-" + props.property.name); @@ -266,7 +265,7 @@ export function ComponentPropertyField(props: Props) { </Tooltip>} {(!showEditor || property.secret) && <TextInput className="text-field" isRequired ref={ref} - type={property.secret && !showPassword ? "password" : "text"} + type="text" autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -299,13 +298,6 @@ export function ComponentPropertyField(props: Props) { setCheckChanges(false); }}/> </InputGroupItem>} - {property.secret && - <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => setShowPassword(!showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip> - } <InputGroupItem> <PropertyPlaceholderDropdown property={property} value={value} onComponentPropertyChange={(parameter, v) => { onParametersChange(parameter, v); @@ -322,7 +314,7 @@ export function ComponentPropertyField(props: Props) { <InputGroupItem isFill> <TextInput className="text-field" isRequired - type={(property.secret ? "password" : "text")} + type="text" autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} diff --git a/karavan-designer/src/designer/property/property/KameletPropertyField.tsx b/karavan-designer/src/designer/property/property/KameletPropertyField.tsx index 0e1e2bf0..d70582b5 100644 --- a/karavan-designer/src/designer/property/property/KameletPropertyField.tsx +++ b/karavan-designer/src/designer/property/property/KameletPropertyField.tsx @@ -51,7 +51,6 @@ export function KameletPropertyField(props: Props) { const [dark] = useDesignerStore((s) => [s.dark], shallow) const [showEditor, setShowEditor] = useState<boolean>(false); - const [showPassword, setShowPassword] = useState<boolean>(false); const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); @@ -171,7 +170,7 @@ export function KameletPropertyField(props: Props) { <TextInput ref={ref} className="text-field" isRequired - type={property.format && !showPassword ? "password" : "text"} + type='text' autoComplete="off" id={id} name={id} value={textValue} @@ -220,13 +219,6 @@ export function KameletPropertyField(props: Props) { setCheckChanges(true); }}/> </InputGroupItem> - {property.format === "password" && - <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => setShowPassword(!showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip> - } </InputGroup> } diff --git a/karavan-designer/src/designer/utils/ValidatorUtils.ts b/karavan-designer/src/designer/utils/ValidatorUtils.ts new file mode 100644 index 00000000..b9240dad --- /dev/null +++ b/karavan-designer/src/designer/utils/ValidatorUtils.ts @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, 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 KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function isSensitiveFieldValid(field: string): boolean { + if (field === undefined || field.trim() === "") { + return true; + } + if (field.startsWith("{{") && field.endsWith("}}")) { + const content = field.slice(2, -2).trim(); + return content !== ""; + } + return false; +} + + diff --git a/karavan-space/src/designer/property/property/BeanProperties.tsx b/karavan-space/src/designer/property/property/BeanProperties.tsx index 34c93cca..d49cd985 100644 --- a/karavan-space/src/designer/property/property/BeanProperties.tsx +++ b/karavan-space/src/designer/property/property/BeanProperties.tsx @@ -16,13 +16,18 @@ */ import React, {useEffect, useState} from 'react'; import { - TextInput, Button, Tooltip, Popover, InputGroup, InputGroupItem, capitalize, + Button, + capitalize, + InputGroup, + InputGroupItem, + Popover, + TextInput, + Tooltip, + ValidatedOptions, } from '@patternfly/react-core'; import '../../karavan.css'; import "@patternfly/patternfly/patternfly.css"; -import { - BeanFactoryDefinition, -} from "karavan-core/lib/model/CamelDefinition"; +import {BeanFactoryDefinition,} from "karavan-core/lib/model/CamelDefinition"; import {CamelUtil} from "karavan-core/lib/api/CamelUtil"; import {SensitiveKeys} from "karavan-core/lib/model/CamelMetadata"; import {v4 as uuidv4} from "uuid"; @@ -31,13 +36,11 @@ import AddIcon from "@patternfly/react-icons/dist/js/icons/plus-circle-icon"; import HelpIcon from "@patternfly/react-icons/dist/js/icons/help-icon"; import {InfrastructureSelector} from "./InfrastructureSelector"; import {InfrastructureAPI} from "../../utils/InfrastructureAPI"; -import ShowIcon from "@patternfly/react-icons/dist/js/icons/eye-icon"; -import HideIcon from "@patternfly/react-icons/dist/js/icons/eye-slash-icon"; import DockerIcon from "@patternfly/react-icons/dist/js/icons/docker-icon"; import {useDesignerStore} from "../../DesignerStore"; import {shallow} from "zustand/shallow"; import {KubernetesIcon} from "../../icons/ComponentIcons"; - +import {isSensitiveFieldValid} from "../../utils/ValidatorUtils"; interface Props { type: 'constructors' | 'properties' @@ -51,26 +54,26 @@ export function BeanProperties (props: Props) { const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [infrastructureSelectorUuid, setInfrastructureSelectorUuid] = useState<string | undefined>(undefined); - const [properties, setProperties] = useState<Map<string, [string, string, boolean]>>(new Map<string, [string, string, boolean]>()); - const [constructors, setConstructors] = useState<Map<string, [number, string, boolean]>>(new Map<string, [number, string, boolean]>()); + const [properties, setProperties] = useState<Map<string, [string, string]>>(new Map<string, [string, string]>()); + const [constructors, setConstructors] = useState<Map<string, [number, string]>>(new Map<string, [number, string]>()); useEffect(()=> { setProperties(preparePropertiesMap((selectedStep as BeanFactoryDefinition)?.properties)) setConstructors(prepareConstructorsMap((selectedStep as BeanFactoryDefinition)?.constructors)) }, [selectedStep?.uuid]) - function preparePropertiesMap (properties: any): Map<string, [string, string, boolean]> { - const result = new Map<string, [string, string, boolean]>(); + function preparePropertiesMap (properties: any): Map<string, [string, string]> { + const result = new Map<string, [string, string]>(); if (properties) { - Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k], false])); + Object.keys(properties).forEach((k, i, a) => result.set(uuidv4(), [k, properties[k]])); } return result; } - function prepareConstructorsMap (constructors: any): Map<string, [number, string, boolean]> { - const result = new Map<string, [number, string, boolean]>(); + function prepareConstructorsMap (constructors: any): Map<string, [number, string]> { + const result = new Map<string, [number, string]>(); if (constructors) { - Object.keys(constructors).forEach((k, i, a) => result.set(uuidv4(), [parseInt(k), constructors[k], false])); + Object.keys(constructors).forEach((k, i, a) => result.set(uuidv4(), [parseInt(k), constructors[k]])); } return result; } @@ -103,17 +106,17 @@ export function BeanProperties (props: Props) { } } - function propertyChanged (uuid: string, key: string, value: string, showPassword: boolean) { + function propertyChanged (uuid: string, key: string, value: string) { setProperties(prevState => { - prevState.set(uuid, [key, value, showPassword]); + prevState.set(uuid, [key, value]); return prevState; }); onBeanPropertyUpdate(); } - function constructorChanged (uuid: string, key: number, value: string, showPassword: boolean) { + function constructorChanged (uuid: string, key: number, value: string) { setConstructors(prevState => { - prevState.set(uuid, [key, value, showPassword]); + prevState.set(uuid, [key, value]); return prevState; }); onBeanConstructorsUpdate(); @@ -140,7 +143,7 @@ export function BeanProperties (props: Props) { const uuid = infrastructureSelectorUuid; if (propertyId && uuid){ if (value.startsWith("config") || value.startsWith("secret")) value = "{{" + value + "}}"; - propertyChanged(uuid, propertyId, value, false); + propertyChanged(uuid, propertyId, value); setInfrastructureSelector(false); setInfrastructureSelectorProperty(undefined); } @@ -201,20 +204,19 @@ export function BeanProperties (props: Props) { const i = v[0]; const key = v[1][0]; const value = v[1][1]; - const showPassword = v[1][2]; const isSecret = false; return ( <div key={"key-" + i} className="bean-property"> <TextInput placeholder="Argument Index" className="text-field" isRequired type="text" id={"key-" + i} name={"key-" + i} value={key} onChange={(_, beanFieldName) => { - constructorChanged(i, parseInt(beanFieldName) , value, showPassword) + constructorChanged(i, parseInt(beanFieldName) , value) }}/> <InputGroup> <InputGroupItem isFill> <TextInput placeholder="Argument Value" - type={isSecret && !showPassword ? "password" : "text"} + type='text' autoComplete="off" className="text-field" isRequired @@ -222,14 +224,9 @@ export function BeanProperties (props: Props) { name={"value-" + i} value={value} onChange={(_, value) => { - constructorChanged(i, key, value, showPassword) + constructorChanged(i, key, value) }}/> </InputGroupItem> - {isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => constructorChanged(i, key, value, !showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip>} </InputGroup> <Button variant="link" className="delete-button" onClick={e => constructorDeleted(i)}> <DeleteIcon/> @@ -237,7 +234,7 @@ export function BeanProperties (props: Props) { </div> ) })} - <Button variant="link" className="add-button" onClick={e => constructorChanged(uuidv4(), constructors.size, '', false)}> + <Button variant="link" className="add-button" onClick={e => constructorChanged(uuidv4(), constructors.size, '')}> <AddIcon/>Add argument</Button> </> ) @@ -250,8 +247,8 @@ export function BeanProperties (props: Props) { const i = v[0]; const key = v[1][0]; const value = v[1][1]; - const showPassword = v[1][2]; const isSecret = key !== undefined && SensitiveKeys.includes(key.toLowerCase()); + const validated = (isSecret && !isSensitiveFieldValid(value)) ? ValidatedOptions.error : ValidatedOptions.default; const inInfrastructure = InfrastructureAPI.infrastructure !== 'local'; const icon = InfrastructureAPI.infrastructure === 'kubernetes' ? KubernetesIcon("infra-button"): <DockerIcon/> return ( @@ -259,11 +256,11 @@ export function BeanProperties (props: Props) { <TextInput placeholder="Bean Field Name" className="text-field" isRequired type="text" id={"key-" + i} name={"key-" + i} value={key} onChange={(_, beanFieldName) => { - propertyChanged(i, beanFieldName, value, showPassword) + propertyChanged(i, beanFieldName, value) }}/> <InputGroup> {inInfrastructure && - <Tooltip position="bottom-end" content={"Select from " + capitalize(InfrastructureAPI.infrastructure)}> + <Tooltip position="bottom-end" content={'Select from ' + capitalize(InfrastructureAPI.infrastructure)}> <Button variant="control" onClick={e => openInfrastructureSelector(i, key)}> {icon} </Button> @@ -271,28 +268,24 @@ export function BeanProperties (props: Props) { <InputGroupItem isFill> <TextInput placeholder="Bean Field Value" - type={isSecret && !showPassword ? "password" : "text"} + type='text' autoComplete="off" className="text-field" isRequired + validated={validated} id={"value-" + i} name={"value-" + i} value={value} onChange={(_, value) => { - propertyChanged(i, key, value, showPassword) + propertyChanged(i, key, value) }}/> </InputGroupItem> - {isSecret && <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => propertyChanged(i, key, value, !showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip>} </InputGroup> <Button variant="link" className="delete-button" onClick={e => propertyDeleted(i)}><DeleteIcon/></Button> </div> ) })} - <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '', false)}> + <Button variant="link" className="add-button" onClick={e => propertyChanged(uuidv4(), '', '')}> <AddIcon/>Add property</Button> </> ) diff --git a/karavan-space/src/designer/property/property/ComponentPropertyField.tsx b/karavan-space/src/designer/property/property/ComponentPropertyField.tsx index 27e22780..57ff7d4c 100644 --- a/karavan-space/src/designer/property/property/ComponentPropertyField.tsx +++ b/karavan-space/src/designer/property/property/ComponentPropertyField.tsx @@ -78,7 +78,6 @@ export function ComponentPropertyField(props: Props) { const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); const [showEditor, setShowEditor] = useState<boolean>(false); - const [showPassword, setShowPassword] = useState<boolean>(false); const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [id, setId] = useState<string>(prefix + "-" + props.property.name); @@ -266,7 +265,7 @@ export function ComponentPropertyField(props: Props) { </Tooltip>} {(!showEditor || property.secret) && <TextInput className="text-field" isRequired ref={ref} - type={property.secret && !showPassword ? "password" : "text"} + type="text" autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} @@ -299,13 +298,6 @@ export function ComponentPropertyField(props: Props) { setCheckChanges(false); }}/> </InputGroupItem>} - {property.secret && - <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => setShowPassword(!showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip> - } <InputGroupItem> <PropertyPlaceholderDropdown property={property} value={value} onComponentPropertyChange={(parameter, v) => { onParametersChange(parameter, v); @@ -322,7 +314,7 @@ export function ComponentPropertyField(props: Props) { <InputGroupItem isFill> <TextInput className="text-field" isRequired - type={(property.secret ? "password" : "text")} + type="text" autoComplete="off" id={id} name={id} value={(textValue !== undefined ? textValue : property.defaultValue) || ''} diff --git a/karavan-space/src/designer/property/property/KameletPropertyField.tsx b/karavan-space/src/designer/property/property/KameletPropertyField.tsx index 0e1e2bf0..d70582b5 100644 --- a/karavan-space/src/designer/property/property/KameletPropertyField.tsx +++ b/karavan-space/src/designer/property/property/KameletPropertyField.tsx @@ -51,7 +51,6 @@ export function KameletPropertyField(props: Props) { const [dark] = useDesignerStore((s) => [s.dark], shallow) const [showEditor, setShowEditor] = useState<boolean>(false); - const [showPassword, setShowPassword] = useState<boolean>(false); const [infrastructureSelector, setInfrastructureSelector] = useState<boolean>(false); const [infrastructureSelectorProperty, setInfrastructureSelectorProperty] = useState<string | undefined>(undefined); const [selectStatus, setSelectStatus] = useState<Map<string, boolean>>(new Map<string, boolean>()); @@ -171,7 +170,7 @@ export function KameletPropertyField(props: Props) { <TextInput ref={ref} className="text-field" isRequired - type={property.format && !showPassword ? "password" : "text"} + type='text' autoComplete="off" id={id} name={id} value={textValue} @@ -220,13 +219,6 @@ export function KameletPropertyField(props: Props) { setCheckChanges(true); }}/> </InputGroupItem> - {property.format === "password" && - <Tooltip position="bottom-end" content={showPassword ? "Hide" : "Show"}> - <Button variant="control" onClick={e => setShowPassword(!showPassword)}> - {showPassword ? <ShowIcon/> : <HideIcon/>} - </Button> - </Tooltip> - } </InputGroup> } diff --git a/karavan-space/src/designer/utils/ValidatorUtils.ts b/karavan-space/src/designer/utils/ValidatorUtils.ts new file mode 100644 index 00000000..b9240dad --- /dev/null +++ b/karavan-space/src/designer/utils/ValidatorUtils.ts @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, 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 KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function isSensitiveFieldValid(field: string): boolean { + if (field === undefined || field.trim() === "") { + return true; + } + if (field.startsWith("{{") && field.endsWith("}}")) { + const content = field.slice(2, -2).trim(); + return content !== ""; + } + return false; +} + +
