From 2f85d09d92291caad85db5d25e62cf052a8f5334 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 12 Jan 2021 12:36:01 +0530 Subject: [PATCH 1/4] Change wizard help panel UIs --- .../minimal-application-create-wizard.tsx | 2 +- .../oauth-protocol-settings-wizard-form.tsx | 17 +-- .../saml-protocol-settings-wizard-form.tsx | 115 +++++++++--------- .../create-wizard-help.tsx | 8 +- .../create-wizard-help.tsx | 6 - .../src/translations/en-US/portals/console.ts | 2 +- .../src/translations/fr-FR/portals/console.ts | 2 +- .../src/translations/si-LK/portals/console.ts | 2 +- .../default/collections/message.overrides | 12 ++ 9 files changed, 87 insertions(+), 79 deletions(-) diff --git a/apps/console/src/features/applications/components/wizard/minimal-application-create-wizard.tsx b/apps/console/src/features/applications/components/wizard/minimal-application-create-wizard.tsx index d7652294292..d48dc0824dc 100644 --- a/apps/console/src/features/applications/components/wizard/minimal-application-create-wizard.tsx +++ b/apps/console/src/features/applications/components/wizard/minimal-application-create-wizard.tsx @@ -370,7 +370,7 @@ export const MinimalAppCreateWizard: FunctionComponent - )} + ) } - + handleAddAllowOrigin(url) } @@ -445,11 +445,14 @@ export const OauthProtocolSettingsWizardForm: FunctionComponent - - { - t("console:develop.features.applications.forms.inboundOIDC.fields" + - ".callBackUrls.validations.required") - } + + + + { + t("console:develop.features.applications.forms.inboundOIDC.fields" + + ".callBackUrls.validations.required") + } + diff --git a/apps/console/src/features/applications/components/wizard/saml-protocol-settings-wizard-form.tsx b/apps/console/src/features/applications/components/wizard/saml-protocol-settings-wizard-form.tsx index ead55e9a24f..2a57f9023e3 100644 --- a/apps/console/src/features/applications/components/wizard/saml-protocol-settings-wizard-form.tsx +++ b/apps/console/src/features/applications/components/wizard/saml-protocol-settings-wizard-form.tsx @@ -157,7 +157,7 @@ export const SAMLProtocolSettingsWizardForm: FunctionComponent { (!fields || fields.includes("issuer")) && ( - + - + ) } { (!fields || fields.includes("assertionConsumerURLs")) && ( - { + + + { - if (!URLUtils.isHttpsOrHttpUrl(value)) { - label = ( - - ); - } + let label: ReactElement = null; - if (!URLUtils.isMobileDeepLink(value)) { - return false; - } + if (!URLUtils.isHttpsOrHttpUrl(value)) { + label = ( + + ); + } - setAssertionConsumerURLsErrorLabel(label); + if (!URLUtils.isMobileDeepLink(value)) { + return false; + } - return true; - } } - computerWidth={ 10 } - required={ true } - showError={ showAssertionConsumerUrlError } - setShowError={ setAssertionConsumerUrlError } - hint={ - !hideFieldHints && t("console:develop.features.applications.forms.inboundSAML" + - ".fields.assertionURLs.hint") - } - addURLTooltip={ t("common:addURL") } - duplicateURLErrorMessage={ t("common:duplicateURLError") } - data-testid={ `${ testId }-assertion-consumer-url-input` } - getSubmit={ (submitFunction: (callback: (url?: string) => void) => void) => { - submitUrl = submitFunction; - } } - showPredictions={ false } - customLabel={ assertionConsumerURLsErrorLabel } - /> + setAssertionConsumerURLsErrorLabel(label); + + return true; + } } + computerWidth={ 10 } + required={ true } + showError={ showAssertionConsumerUrlError } + setShowError={ setAssertionConsumerUrlError } + hint={ + !hideFieldHints && t("console:develop.features.applications" + + ".forms.inboundSAML.fields.assertionURLs.hint") + } + addURLTooltip={ t("common:addURL") } + duplicateURLErrorMessage={ t("common:duplicateURLError") } + data-testid={ `${ testId }-assertion-consumer-url-input` } + getSubmit={ (submitFunction: (callback: (url?: string) => void) => void) => { + submitUrl = submitFunction; + } } + showPredictions={ false } + customLabel={ assertionConsumerURLsErrorLabel } + /> + + ) } diff --git a/apps/console/src/features/applications/data/application-templates/templates/oidc-web-application/create-wizard-help.tsx b/apps/console/src/features/applications/data/application-templates/templates/oidc-web-application/create-wizard-help.tsx index c690271c014..be3a24cbf0b 100644 --- a/apps/console/src/features/applications/data/application-templates/templates/oidc-web-application/create-wizard-help.tsx +++ b/apps/console/src/features/applications/data/application-templates/templates/oidc-web-application/create-wizard-help.tsx @@ -54,12 +54,6 @@ const OIDCWebApplicationCreateWizardHelp: FunctionComponent - - - Click here - { " " } - to learn more about supported protocols for agent-based single sign-on. - @@ -71,7 +65,7 @@ const OIDCWebApplicationCreateWizardHelp: FunctionComponent -

E.g. https://www.conotoso.com/login

+

E.g. https://sample.app/login

You can also configure this field later under the Protocol diff --git a/apps/console/src/features/applications/data/application-templates/templates/saml-web-application/create-wizard-help.tsx b/apps/console/src/features/applications/data/application-templates/templates/saml-web-application/create-wizard-help.tsx index d38528e99ef..75750099503 100644 --- a/apps/console/src/features/applications/data/application-templates/templates/saml-web-application/create-wizard-help.tsx +++ b/apps/console/src/features/applications/data/application-templates/templates/saml-web-application/create-wizard-help.tsx @@ -54,12 +54,6 @@ const SAMLWebApplicationCreateWizardHelp: FunctionComponent - - - Click here - { " " } - to learn more about supported protocols for agent-based single sign-on. - diff --git a/modules/i18n/src/translations/en-US/portals/console.ts b/modules/i18n/src/translations/en-US/portals/console.ts index faa43827255..36bb669d9e4 100644 --- a/modules/i18n/src/translations/en-US/portals/console.ts +++ b/modules/i18n/src/translations/en-US/portals/console.ts @@ -1049,7 +1049,7 @@ export const console: ConsoleNS = { placeholder: "Enter allowed redirect URLs", validations: { empty: "Please add a valid URL.", - required: "Note: This field is required for a functional app. " + + required: "This field is required for a functional app. " + "However, if you are planning to integrate a sample, " + "this field can be skipped." } diff --git a/modules/i18n/src/translations/fr-FR/portals/console.ts b/modules/i18n/src/translations/fr-FR/portals/console.ts index 8db7d5ae6e2..7215e0a283e 100644 --- a/modules/i18n/src/translations/fr-FR/portals/console.ts +++ b/modules/i18n/src/translations/fr-FR/portals/console.ts @@ -1039,7 +1039,7 @@ export const console: ConsoleNS = { placeholder: "Saisir les URLs de redirection", validations: { empty: "Veuillez ajouter une URL valide.", - required: "Remarque: ce champ est obligatoire pour une application fonctionnelle." + required: "ce champ est obligatoire pour une application fonctionnelle." } }, clientID: { diff --git a/modules/i18n/src/translations/si-LK/portals/console.ts b/modules/i18n/src/translations/si-LK/portals/console.ts index 8ece12eef6c..a4f06090f02 100644 --- a/modules/i18n/src/translations/si-LK/portals/console.ts +++ b/modules/i18n/src/translations/si-LK/portals/console.ts @@ -1060,7 +1060,7 @@ export const console: ConsoleNS = { placeholder: "යළි-යොමුවීම් URL ඇතුළත් කරන්න", validations: { empty: "කරුණාකර වලංගු URL එකක් එක් කරන්න.", - required: "සටහන: ක්‍රියාකාරී යෙදුමක් සඳහා මෙම ක්ෂේත්‍රය අවශ්‍ය වේ." + required: "ක්‍රියාකාරී යෙදුමක් සඳහා මෙම ක්ෂේත්‍රය අවශ්‍ය වේ." } }, clientID: { diff --git a/modules/theme/src/themes/default/collections/message.overrides b/modules/theme/src/themes/default/collections/message.overrides index d4cfbb9b3b7..454f81db3ec 100644 --- a/modules/theme/src/themes/default/collections/message.overrides +++ b/modules/theme/src/themes/default/collections/message.overrides @@ -24,3 +24,15 @@ } } } + +/*------------------------ + Message With Icon +-------------------------*/ + +.ui.message { + &.with-inline-icon { + >.icon:not(.close) { + font-size: 2em; + } + } +} From 84359aa8a4a137d888579225cc08bbca1dfeb5e8 Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 12 Jan 2021 18:49:34 +0530 Subject: [PATCH 2/4] Move certificate to protocol form --- .../components/edit-application.tsx | 2 +- .../components/forms/general-details-form.tsx | 257 +------------- .../components/forms/inbound-custom-form.tsx | 247 +++++++++++++- .../components/forms/inbound-form-factory.tsx | 12 +- .../components/forms/inbound-oidc-form.tsx | 241 +++++++++++++- .../forms/inbound-passive-sts-form.tsx | 225 ++++++++++++- .../components/forms/inbound-saml-form.tsx | 314 +++++++++++++++--- .../forms/inbound-ws-trust-form.tsx | 235 ++++++++++++- .../modals/certificate-form-field-modal.tsx | 108 ++++++ .../applications/components/modals/index.ts | 19 ++ .../settings/access-configuration.tsx | 50 ++- .../settings/general-application-settings.tsx | 9 +- .../i18n/src/models/namespaces/console-ns.ts | 3 + .../src/translations/en-US/portals/console.ts | 16 + .../src/translations/fr-FR/portals/console.ts | 16 + .../src/translations/si-LK/portals/console.ts | 16 + 16 files changed, 1415 insertions(+), 355 deletions(-) create mode 100644 apps/console/src/features/applications/components/modals/certificate-form-field-modal.tsx create mode 100644 apps/console/src/features/applications/components/modals/index.ts diff --git a/apps/console/src/features/applications/components/edit-application.tsx b/apps/console/src/features/applications/components/edit-application.tsx index 08810df02d9..8a8372d12bd 100755 --- a/apps/console/src/features/applications/components/edit-application.tsx +++ b/apps/console/src/features/applications/components/edit-application.tsx @@ -390,7 +390,6 @@ export const EditApplication: FunctionComponent = = setIsAllowedOriginsUpdated(!isAllowedOriginsUpdated) } onApplicationSecretRegenerate={ handleApplicationSecretRegenerate } appId={ application.id } diff --git a/apps/console/src/features/applications/components/forms/general-details-form.tsx b/apps/console/src/features/applications/components/forms/general-details-form.tsx index a42b61aebe7..4567a036518 100644 --- a/apps/console/src/features/applications/components/forms/general-details-form.tsx +++ b/apps/console/src/features/applications/components/forms/general-details-form.tsx @@ -16,19 +16,15 @@ * under the License. */ -import { AlertLevels, DisplayCertificate, TestableComponentInterface } from "@wso2is/core/models"; -import { addAlert } from "@wso2is/core/store"; -import { CertificateManagementUtils } from "@wso2is/core/utils"; +import { TestableComponentInterface } from "@wso2is/core/models"; import { Field, FormValue, Forms, Validation } from "@wso2is/forms"; -import { Certificate as CertificateDisplay, GenericIcon, Heading, Hint, LinkButton } from "@wso2is/react-components"; +import { Hint } from "@wso2is/react-components"; import { FormValidation } from "@wso2is/validation"; -import _ from "lodash"; -import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; +import React, { FunctionComponent, ReactElement, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; -import { Button, Grid, Icon, Modal } from "semantic-ui-react"; -import { AppConstants, AppState, UIConfigInterface, getCertificateIllustrations } from "../../../core"; -import { CertificateInterface, CertificateTypeInterface } from "../../models"; +import { useSelector } from "react-redux"; +import { Button, Grid, Icon } from "semantic-ui-react"; +import { AppConstants, AppState, UIConfigInterface} from "../../../core"; /** * Proptypes for the applications general details form component. @@ -70,10 +66,6 @@ interface GeneralDetailsFormPopsInterface extends TestableComponentInterface { * Make the form read only. */ readOnly?: boolean; - /** - * Current certificate configurations. - */ - certificate: CertificateInterface; } /** @@ -97,7 +89,6 @@ export const GeneralDetailsForm: FunctionComponent state?.config?.ui); const [ isDiscoverable, setDiscoverability ] = useState(discoverability); - const [ isPEMSelected, setPEMSelected ] = useState(false); - const [ certificateModal, setCertificateModal ] = useState(false); - const [ PEMValue, setPEMValue ] = useState(undefined); - const [ certificateDisplay, setCertificateDisplay ] = useState(null); - - const dispatch = useDispatch(); - - /** - * Set initial PEM values. - */ - useEffect(() => { - if (CertificateTypeInterface.PEM === certificate?.type) { - setPEMSelected(true); - if (certificate?.value) { - setPEMValue(certificate.value); - } - } - }, [ certificate ]); /** * Prepare form values for submitting. @@ -135,10 +108,6 @@ export const GeneralDetailsForm: FunctionComponent { - return ( - { - setCertificateModal(false); - } } - data-testid={ `${ testId }-view-certificate-modal` } - > - -

- -
- View Certificate - { - certificateDisplay?.alias - ? certificateDisplay?.alias - : certificateDisplay?.issuerDN && ( - CertificateManagementUtils.searchIssuerDNAlias(certificateDisplay?.issuerDN) - ) - } -

-
Serial Number: { certificateDisplay?.serialNumber }
-
- - - - - - ); - }; - - /** - * Construct the details from the pem value. - */ - const viewCertificate = () => { - if (isPEMSelected && PEMValue) { - const displayCertificate: DisplayCertificate = CertificateManagementUtils.displayCertificate( - null, PEMValue); - - if (displayCertificate) { - setCertificateDisplay(displayCertificate); - setCertificateModal(true); - } else { - dispatch(addAlert({ - description: "Provided pem is malformed", - level: AlertLevels.ERROR, - message: "Decode Error" - })); - } - } - }; - - /** - * Handle view certificate. - * - * @param event Button click event. - */ - const handleCertificateView = (event: React.MouseEvent) => { - event.preventDefault(); - viewCertificate(); - }; - /** * Handles form value change. * @@ -406,138 +293,6 @@ export const GeneralDetailsForm: FunctionComponent
- - - - { t("console:develop.features.applications.forms.advancedConfig.sections.certificate" + - ".heading") } - - { - setPEMSelected(values.get("type") === "PEM"); - } - } - type="radio" - value={ certificate?.type } - children={ [ - { - label: t("console:develop.features.applications.forms.advancedConfig.sections" + - ".certificate.fields.type.children.jwks.label"), - value: CertificateTypeInterface.JWKS - }, - { - label: t("console:develop.features.applications.forms.advancedConfig.sections" + - ".certificate.fields.type.children.pem.label"), - value: CertificateTypeInterface.PEM - } - ] } - readOnly={ readOnly } - data-testid={ `${ testId }-certificate-type-radio-group` } - /> - - - - - { - isPEMSelected - ? - ( - <> - { - setPEMValue( - values.get("certificateValue") as string - ); - } - } - readOnly={ readOnly } - data-testid={ `${ testId }-certificate-textarea` } - /> - < Hint> - { t("console:develop.features.applications.forms.advancedConfig.sections" + - ".certificate.fields.pemValue.hint") } - - - { t("console:develop.features.applications.forms.advancedConfig.sections" + - ".certificate.fields.pemValue.actions.view") } - - - ) - : ( - <> - { - if (!FormValidation.url(value)) { - validation.isValid = false; - validation.errorMessages.push( - t( - "console:develop.features.applications.forms" + - ".advancedConfig.sections.certificate.fields.jwksValue" + - ".validations.invalid" - ) - ); - } - } } - value={ - (CertificateTypeInterface.JWKS === certificate?.type) - && certificate?.value - } - readOnly={ readOnly } - data-testid={ `${ testId }-jwks-input` } - /> - - ) - } - - - - { certificateModal && renderCertificateModal() } { !readOnly && ( diff --git a/apps/console/src/features/applications/components/forms/inbound-custom-form.tsx b/apps/console/src/features/applications/components/forms/inbound-custom-form.tsx index 740870dd4b4..1e13db78941 100644 --- a/apps/console/src/features/applications/components/forms/inbound-custom-form.tsx +++ b/apps/console/src/features/applications/components/forms/inbound-custom-form.tsx @@ -16,12 +16,19 @@ * under the License. */ -import { TestableComponentInterface } from "@wso2is/core/models"; -import { Field, Forms } from "@wso2is/forms"; -import React, { FunctionComponent, ReactElement, useEffect } from "react"; +import { AlertInterface, AlertLevels, DisplayCertificate, TestableComponentInterface } from "@wso2is/core/models"; +import { addAlert } from "@wso2is/core/store"; +import { CertificateManagementUtils } from "@wso2is/core/utils"; +import { Field, Forms, Validation } from "@wso2is/forms"; +import { Heading, Hint, LinkButton } from "@wso2is/react-components"; +import { FormValidation } from "@wso2is/validation"; +import isEmpty from "lodash/isEmpty"; +import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button, Grid } from "semantic-ui-react"; +import { useDispatch } from "react-redux"; +import { Button, Divider, Grid } from "semantic-ui-react"; import { + CertificateInterface, CertificateTypeInterface, CustomInboundProtocolConfigurationInterface, CustomInboundProtocolMetaDataInterface, CustomInboundProtocolPropertyInterface, @@ -29,14 +36,23 @@ import { PropertyModelInterface, SubmitFormCustomPropertiesInterface } from "../../models"; +import { CertificateFormFieldModal } from "../modals"; /** * Proptypes for the inbound custom protocol form component. */ interface InboundCustomFormPropsInterface extends TestableComponentInterface { + /** + * Current certificate configurations. + */ + certificate: CertificateInterface; metadata?: CustomInboundProtocolMetaDataInterface; initialValues?: CustomInboundProtocolConfigurationInterface; onSubmit: (values: any) => void; + /** + * Make the form read only. + */ + readOnly?: boolean; } /** @@ -51,14 +67,35 @@ export const InboundCustomProtocolForm: FunctionComponent { const { + certificate, metadata, initialValues, onSubmit, + readOnly, [ "data-testid" ]: testId } = props; const { t } = useTranslation(); + const dispatch = useDispatch(); + + const [ isPEMSelected, setPEMSelected ] = useState(false); + const [ showCertificateModal, setShowCertificateModal ] = useState(false); + const [ PEMValue, setPEMValue ] = useState(undefined); + const [ certificateDisplay, setCertificateDisplay ] = useState(null); + + /** + * Set initial PEM values. + */ + useEffect(() => { + if (CertificateTypeInterface.PEM === certificate?.type) { + setPEMSelected(true); + if (certificate?.value) { + setPEMValue(certificate.value); + } + } + }, [ certificate ]); + const createInputComponent = ( (config: CustomInboundProtocolPropertyInterface, initialValue?: PropertyModelInterface) => { if (config?.availableValues?.length > 0) { @@ -219,11 +256,21 @@ export const InboundCustomProtocolForm: FunctionComponent { + if (isPEMSelected && PEMValue) { + const displayCertificate: DisplayCertificate = CertificateManagementUtils.displayCertificate( + null, PEMValue); + + if (displayCertificate) { + setCertificateDisplay(displayCertificate); + setShowCertificateModal(true); + } else { + dispatch(addAlert({ + description: t("console:common.notifications.invalidPEMFile.genericError.description"), + level: AlertLevels.ERROR, + message: t("console:common.notifications.invalidPEMFile.genericError.message") + })); + } + } + }; + return ( { @@ -241,13 +309,168 @@ export const InboundCustomProtocolForm: FunctionComponent { generateFormElements() } + { /* Certificates */ } + + + + + + + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.heading") } + + { + setPEMSelected(values.get("type") === "PEM"); + } + } + type="radio" + value={ certificate?.type } + children={ [ + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.jwks.label"), + value: CertificateTypeInterface.JWKS + }, + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.pem.label"), + value: CertificateTypeInterface.PEM + } + ] } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-type-radio-group` } + /> + + - + { + isPEMSelected + ? + ( + <> + { + setPEMValue( + values.get("certificateValue") as string + ); + } + } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-textarea` } + /> + < Hint> + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue.hint") + } + + viewCertificate() } + disabled={ isEmpty(PEMValue) } + data-testid={ `${ testId }-certificate-info-button` } + > + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue." + + "actions.view") + } + + + ) + : ( + <> + { + if (!FormValidation.url(value)) { + validation.isValid = false; + validation.errorMessages.push( + t( + "console:develop.features.applications.forms" + + ".advancedConfig.sections.certificate.fields." + + "jwksValue.validations.invalid" + ) + ); + } + } } + value={ + (CertificateTypeInterface.JWKS === certificate?.type) + && certificate?.value + } + readOnly={ readOnly } + data-testid={ `${ testId }-jwks-input` } + /> + + ) + } + { + showCertificateModal && ( + { + setShowCertificateModal(false); + } } + /> + ) + } + { + !readOnly && ( + + + + + + ) + } ); diff --git a/apps/console/src/features/applications/components/forms/inbound-form-factory.tsx b/apps/console/src/features/applications/components/forms/inbound-form-factory.tsx index 9deceec5889..a9b48df6478 100644 --- a/apps/console/src/features/applications/components/forms/inbound-form-factory.tsx +++ b/apps/console/src/features/applications/components/forms/inbound-form-factory.tsx @@ -23,12 +23,16 @@ import { InboundOIDCForm } from "./inbound-oidc-form"; import { InboundPassiveStsForm } from "./inbound-passive-sts-form"; import { InboundSAMLForm } from "./inbound-saml-form"; import { InboundWSTrustForm } from "./inbound-ws-trust-form"; -import { ApplicationTemplateListItemInterface, SupportedAuthProtocolTypes } from "../../models"; +import { ApplicationTemplateListItemInterface, CertificateInterface, SupportedAuthProtocolTypes } from "../../models"; /** * Proptypes for the inbound form factory component. */ interface InboundFormFactoryInterface extends TestableComponentInterface { + /** + * Current certificate configurations. + */ + certificate: CertificateInterface; metadata?: any; initialValues: any; onSubmit: (values: any) => void; @@ -65,6 +69,7 @@ export const InboundFormFactory: FunctionComponent ): ReactElement => { const { + certificate, metadata, initialValues, onSubmit, @@ -82,6 +87,7 @@ export const InboundFormFactory: FunctionComponent case SupportedAuthProtocolTypes.OIDC: return ( case SupportedAuthProtocolTypes.SAML: return ( case SupportedAuthProtocolTypes.WS_TRUST: return ( case SupportedAuthProtocolTypes.WS_FEDERATION: return ( case SupportedAuthProtocolTypes.CUSTOM: return ( void; @@ -82,6 +97,7 @@ export const InboundOIDCForm: FunctionComponent = ): ReactElement => { const { + certificate, metadata, initialValues, onSubmit, @@ -96,6 +112,8 @@ export const InboundOIDCForm: FunctionComponent = const { t } = useTranslation(); + const dispatch = useDispatch(); + const isClientSecretHashEnabled: boolean = useSelector((state: AppState) => state.config.ui.isClientSecretHashEnabled); const isHelpPanelVisible: boolean = useSelector((state: AppState) => state.helpPanel.visibility); @@ -116,6 +134,10 @@ export const InboundOIDCForm: FunctionComponent = ] = useState(false); const [ callbackURLsErrorLabel, setCallbackURLsErrorLabel ] = useState(null); const [ allowedOriginsErrorLabel, setAllowedOriginsErrorLabel ] = useState(null); + const [ isPEMSelected, setPEMSelected ] = useState(false); + const [ showCertificateModal, setShowCertificateModal ] = useState(false); + const [ PEMValue, setPEMValue ] = useState(undefined); + const [ certificateDisplay, setCertificateDisplay ] = useState(null); const clientSecret = useRef(); const grant = useRef(); @@ -185,6 +207,18 @@ export const InboundOIDCForm: FunctionComponent = } }, [ initialValues?.accessToken ]); + /** + * Set initial PEM values. + */ + useEffect(() => { + if (CertificateTypeInterface.PEM === certificate?.type) { + setPEMSelected(true); + if (certificate?.value) { + setPEMValue(certificate.value); + } + } + }, [ certificate ]); + /** * Handle grant type change. * @@ -303,7 +337,7 @@ export const InboundOIDCForm: FunctionComponent = * @return {any} Sanitized form values. */ const updateConfiguration = (values: any, url?: string, origin?: string): any => { - let formValues: any = { + let inboundConfigFormValues: any = { accessToken: { applicationAccessTokenExpiryInSeconds: Number(metadata.defaultApplicationAccessTokenExpiryTime), bindingType: values.get("bindingType"), @@ -344,14 +378,14 @@ export const InboundOIDCForm: FunctionComponent = // Add the `allowedOrigins` & `callbackURLs` only if the grant types // `authorization_code` and `implicit` are selected. if (showCallbackURLField) { - formValues = { - ...formValues, + inboundConfigFormValues = { + ...inboundConfigFormValues, allowedOrigins: ApplicationManagementUtils.resolveAllowedOrigins(origin ? origin : allowedOrigins), callbackURLs: [ ApplicationManagementUtils.buildCallBackUrlWithRegExp(url ? url : callBackUrls) ] }; } else { - formValues = { - ...formValues, + inboundConfigFormValues = { + ...inboundConfigFormValues, allowedOrigins: [], callbackURLs: [] }; @@ -359,13 +393,23 @@ export const InboundOIDCForm: FunctionComponent = // If the app is newly created do not add `clientId` & `clientSecret`. if (!initialValues?.clientId || !initialValues?.clientSecret) { - return formValues; + return inboundConfigFormValues; } return { - ...formValues, - clientId: initialValues?.clientId, - clientSecret: initialValues?.clientSecret + general: { + advancedConfigurations: { + certificate: { + type: values.get("type"), + value: isPEMSelected ? values.get("certificateValue") : values.get("jwksValue") + } + } + }, + inbound: { + ...inboundConfigFormValues, + clientId: initialValues?.clientId, + clientSecret: initialValues?.clientSecret + } }; }; @@ -479,6 +523,27 @@ export const InboundOIDCForm: FunctionComponent = */ let submitOrigin: (callback: (origin?: string) => void) => void; + /** + * Construct the details from the pem value. + */ + const viewCertificate = () => { + if (isPEMSelected && PEMValue) { + const displayCertificate: DisplayCertificate = CertificateManagementUtils.displayCertificate( + null, PEMValue); + + if (displayCertificate) { + setCertificateDisplay(displayCertificate); + setShowCertificateModal(true); + } else { + dispatch(addAlert({ + description: t("console:common.notifications.invalidPEMFile.genericError.description"), + level: AlertLevels.ERROR, + message: t("console:common.notifications.invalidPEMFile.genericError.message") + })); + } + } + }; + /** * Renders the list of main OIDC config fields. * @return {ReactElement} @@ -1284,6 +1349,156 @@ export const InboundOIDCForm: FunctionComponent = /> + { /* Certificates */ } + + + + + + + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.heading") } + + { + setPEMSelected(values.get("type") === "PEM"); + } + } + type="radio" + value={ certificate?.type } + children={ [ + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.jwks.label"), + value: CertificateTypeInterface.JWKS + }, + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.pem.label"), + value: CertificateTypeInterface.PEM + } + ] } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-type-radio-group` } + /> + + + + + { + isPEMSelected + ? + ( + <> + { + setPEMValue( + values.get("certificateValue") as string + ); + } + } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-textarea` } + /> + < Hint> + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue.hint") + } + + viewCertificate() } + disabled={ isEmpty(PEMValue) } + data-testid={ `${ testId }-certificate-info-button` } + > + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue.actions.view") + } + + + ) + : ( + <> + { + if (!FormValidation.url(value)) { + validation.isValid = false; + validation.errorMessages.push( + t( + "console:develop.features.applications.forms" + + ".advancedConfig.sections.certificate.fields.jwksValue" + + ".validations.invalid" + ) + ); + } + } } + value={ + (CertificateTypeInterface.JWKS === certificate?.type) + && certificate?.value + } + readOnly={ readOnly } + data-testid={ `${ testId }-jwks-input` } + /> + + ) + } + + + { + showCertificateModal && ( + { + setShowCertificateModal(false); + } } + /> + ) + } { !readOnly && ( diff --git a/apps/console/src/features/applications/components/forms/inbound-passive-sts-form.tsx b/apps/console/src/features/applications/components/forms/inbound-passive-sts-form.tsx index d74d4ceb3ff..781fad93320 100644 --- a/apps/console/src/features/applications/components/forms/inbound-passive-sts-form.tsx +++ b/apps/console/src/features/applications/components/forms/inbound-passive-sts-form.tsx @@ -16,20 +16,29 @@ * under the License. */ -import { TestableComponentInterface } from "@wso2is/core/models"; +import { AlertInterface, AlertLevels, DisplayCertificate, TestableComponentInterface } from "@wso2is/core/models"; +import { addAlert } from "@wso2is/core/store"; +import { CertificateManagementUtils } from "@wso2is/core/utils"; import { Field, Forms, Validation } from "@wso2is/forms"; -import { Hint } from "@wso2is/react-components"; +import { Heading, Hint, LinkButton } from "@wso2is/react-components"; import { FormValidation } from "@wso2is/validation"; import _ from "lodash"; -import React, { FunctionComponent, ReactElement } from "react"; +import isEmpty from "lodash/isEmpty"; +import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button, Grid } from "semantic-ui-react"; -import { PassiveStsConfigurationInterface } from "../../models"; +import { useDispatch } from "react-redux"; +import { Button, Divider, Grid } from "semantic-ui-react"; +import { CertificateInterface, CertificateTypeInterface, PassiveStsConfigurationInterface } from "../../models"; +import { CertificateFormFieldModal } from "../modals"; /** * Proptypes for the inbound Passive Sts form component. */ interface InboundPassiveStsFormPropsInterface extends TestableComponentInterface { + /** + * Current certificate configurations. + */ + certificate: CertificateInterface; initialValues: PassiveStsConfigurationInterface; onSubmit: (values: any) => void; /** @@ -50,6 +59,7 @@ export const InboundPassiveStsForm: FunctionComponent { const { + certificate, initialValues, onSubmit, readOnly, @@ -58,6 +68,25 @@ export const InboundPassiveStsForm: FunctionComponent(false); + const [ showCertificateModal, setShowCertificateModal ] = useState(false); + const [ PEMValue, setPEMValue ] = useState(undefined); + const [ certificateDisplay, setCertificateDisplay ] = useState(null); + + /** + * Set initial PEM values. + */ + useEffect(() => { + if (CertificateTypeInterface.PEM === certificate?.type) { + setPEMSelected(true); + if (certificate?.value) { + setPEMValue(certificate.value); + } + } + }, [ certificate ]); + /** * Prepares form values for submit. * @@ -67,11 +96,42 @@ export const InboundPassiveStsForm: FunctionComponent { return { - realm: values.get("realm"), - replyTo: values.get("replyTo") + general: { + advancedConfigurations: { + certificate: { + type: values.get("type"), + value: isPEMSelected ? values.get("certificateValue") : values.get("jwksValue") + } + } + }, + inbound: { + realm: values.get("realm"), + replyTo: values.get("replyTo") + } }; }; + /** + * Construct the details from the pem value. + */ + const viewCertificate = () => { + if (isPEMSelected && PEMValue) { + const displayCertificate: DisplayCertificate = CertificateManagementUtils.displayCertificate( + null, PEMValue); + + if (displayCertificate) { + setCertificateDisplay(displayCertificate); + setShowCertificateModal(true); + } else { + dispatch(addAlert({ + description: t("console:common.notifications.invalidPEMFile.genericError.description"), + level: AlertLevels.ERROR, + message: t("console:common.notifications.invalidPEMFile.genericError.message") + })); + } + } + }; + return ( { @@ -136,6 +196,157 @@ export const InboundPassiveStsForm: FunctionComponent + { /* Certificates */ } + + + + + + + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.heading") } + + { + setPEMSelected(values.get("type") === "PEM"); + } + } + type="radio" + value={ certificate?.type } + children={ [ + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.jwks.label"), + value: CertificateTypeInterface.JWKS + }, + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.pem.label"), + value: CertificateTypeInterface.PEM + } + ] } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-type-radio-group` } + /> + + + + + { + isPEMSelected + ? + ( + <> + { + setPEMValue( + values.get("certificateValue") as string + ); + } + } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-textarea` } + /> + < Hint> + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue.hint") + } + + viewCertificate() } + disabled={ isEmpty(PEMValue) } + data-testid={ `${ testId }-certificate-info-button` } + > + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue." + + "actions.view") + } + + + ) + : ( + <> + { + if (!FormValidation.url(value)) { + validation.isValid = false; + validation.errorMessages.push( + t( + "console:develop.features.applications.forms" + + ".advancedConfig.sections.certificate.fields." + + "jwksValue.validations.invalid" + ) + ); + } + } } + value={ + (CertificateTypeInterface.JWKS === certificate?.type) + && certificate?.value + } + readOnly={ readOnly } + data-testid={ `${ testId }-jwks-input` } + /> + + ) + } + + + { + showCertificateModal && ( + { + setShowCertificateModal(false); + } } + /> + ) + } { !readOnly && ( diff --git a/apps/console/src/features/applications/components/forms/inbound-saml-form.tsx b/apps/console/src/features/applications/components/forms/inbound-saml-form.tsx index 96753545fde..3f9ff46fa52 100644 --- a/apps/console/src/features/applications/components/forms/inbound-saml-form.tsx +++ b/apps/console/src/features/applications/components/forms/inbound-saml-form.tsx @@ -16,24 +16,33 @@ * under the License. */ -import { TestableComponentInterface } from "@wso2is/core/models"; -import { URLUtils } from "@wso2is/core/utils"; +import { AlertInterface, AlertLevels, DisplayCertificate, TestableComponentInterface } from "@wso2is/core/models"; +import { addAlert } from "@wso2is/core/store"; +import { CertificateManagementUtils, URLUtils } from "@wso2is/core/utils"; import { Field, Forms, Validation } from "@wso2is/forms"; -import { CopyInputField, Heading, Hint, URLInput } from "@wso2is/react-components"; +import { CopyInputField, Heading, Hint, LinkButton, URLInput } from "@wso2is/react-components"; import { FormValidation } from "@wso2is/validation"; import isEmpty from "lodash/isEmpty"; import union from "lodash/union"; import React, { FunctionComponent, ReactElement, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; import { Button, Divider, Form, Grid, Label } from "semantic-ui-react"; import { + CertificateInterface, + CertificateTypeInterface, LogoutMethods, MetadataPropertyInterface, SAML2ServiceProviderInterface, SAMLMetaDataInterface } from "../../models"; +import { CertificateFormFieldModal } from "../modals"; interface InboundSAMLFormPropsInterface extends TestableComponentInterface { + /** + * Current certificate configurations. + */ + certificate: CertificateInterface; initialValues: SAML2ServiceProviderInterface; metadata: SAMLMetaDataInterface; onSubmit: (values: any) => void; @@ -55,6 +64,7 @@ export const InboundSAMLForm: FunctionComponent = ): ReactElement => { const { + certificate, initialValues, metadata, onSubmit, @@ -64,6 +74,8 @@ export const InboundSAMLForm: FunctionComponent = const { t } = useTranslation(); + const dispatch = useDispatch(); + const [ assertionConsumerURLsErrorLabel, setAssertionConsumerURLsErrorLabel ] = useState(null); const [ audiencesErrorLabel, setAudiencesErrorLabel ] = useState(null); const [ recipientsErrorLabel, setRecipientsErrorLabel ] = useState(null); @@ -104,6 +116,11 @@ export const InboundSAMLForm: FunctionComponent = const [isRequestSignatureValidationEnabled, setIsRequestSignatureValidationEnabled] = useState(false); const [isAssertionEncryptionEnabled, setAssertionEncryptionEnabled] = useState(false); + const [ isPEMSelected, setPEMSelected ] = useState(false); + const [ showCertificateModal, setShowCertificateModal ] = useState(false); + const [ PEMValue, setPEMValue ] = useState(undefined); + const [ certificateDisplay, setCertificateDisplay ] = useState(null); + const issuer = useRef(); const applicationQualifier = useRef(); const consumerURL = useRef(); @@ -134,6 +151,18 @@ export const InboundSAMLForm: FunctionComponent = const returnToURL = useRef(); const assertionQueryProfile = useRef(); + /** + * Set initial PEM values. + */ + useEffect(() => { + if (CertificateTypeInterface.PEM === certificate?.type) { + setPEMSelected(true); + if (certificate?.value) { + setPEMValue(certificate.value); + } + } + }, [ certificate ]); + const createDefaultAssertionConsumerUrl = () => { const allowedOptions = []; if (!isEmpty(assertionConsumerUrls)) { @@ -148,56 +177,66 @@ export const InboundSAMLForm: FunctionComponent = const updateConfiguration = (values) => { return { - manualConfiguration: { - assertionConsumerUrls: assertionConsumerUrls.split(","), - attributeProfile: { - alwaysIncludeAttributesInResponse: values.get("includeAttributesInResponse") - .includes("alwaysIncludeAttributesInResponse"), - enabled: values.get("attributeProfile").includes("enabled") - }, - defaultAssertionConsumerUrl: values.get("defaultAssertionConsumerUrl"), - enableAssertionQueryProfile: - values.get("assertionQueryProfile").includes("enableAssertionQueryProfile"), - idpEntityIdAlias: values.get("idpEntityIdAlias"), - issuer: values.get("issuer") || initialValues?.issuer, - requestValidation: { - enableSignatureValidation: values.get("requestSignatureValidation") - .includes("enableSignatureValidation"), - signatureValidationCertAlias: values.get("signatureValidationCertAlias") - }, - responseSigning: { - enabled: values.get("responseSigning").includes("enabled"), - signingAlgorithm: values.get("signingAlgorithm") - }, - serviceProviderQualifier: values.get("applicationQualifier"), - singleLogoutProfile: { - enabled: values.get("singleLogoutProfile").includes("enabled"), - idpInitiatedSingleLogout: { - enabled: values.get("idpInitiatedSingleLogout").includes("enabled"), - returnToUrls: returnToURLS ? returnToURLS.split(",") : [] + general: { + advancedConfigurations: { + certificate: { + type: values.get("type"), + value: isPEMSelected ? values.get("certificateValue") : values.get("jwksValue") + } + } + }, + inbound: { + manualConfiguration: { + assertionConsumerUrls: assertionConsumerUrls.split(","), + attributeProfile: { + alwaysIncludeAttributesInResponse: values.get("includeAttributesInResponse") + .includes("alwaysIncludeAttributesInResponse"), + enabled: values.get("attributeProfile").includes("enabled") + }, + defaultAssertionConsumerUrl: values.get("defaultAssertionConsumerUrl"), + enableAssertionQueryProfile: + values.get("assertionQueryProfile").includes("enableAssertionQueryProfile"), + idpEntityIdAlias: values.get("idpEntityIdAlias"), + issuer: values.get("issuer") || initialValues?.issuer, + requestValidation: { + enableSignatureValidation: values.get("requestSignatureValidation") + .includes("enableSignatureValidation"), + signatureValidationCertAlias: values.get("signatureValidationCertAlias") + }, + responseSigning: { + enabled: values.get("responseSigning").includes("enabled"), + signingAlgorithm: values.get("signingAlgorithm") }, - logoutMethod: values.get("logoutMethod"), - logoutRequestUrl: values.get("singleLogoutRequestUrl"), - logoutResponseUrl: values.get("singleLogoutResponseUrl") - }, - singleSignOnProfile: { - assertion: { - audiences: audiences ? audiences.split(",") : [], - digestAlgorithm: values.get("digestAlgorithm"), - encryption: { - assertionEncryptionAlgorithm: values.get("assertionEncryptionAlgorithm"), - enabled: values.get("assertionEncryption").includes("enableAssertionEncryption"), - keyEncryptionAlgorithm: values.get("keyEncryptionAlgorithm") + serviceProviderQualifier: values.get("applicationQualifier"), + singleLogoutProfile: { + enabled: values.get("singleLogoutProfile").includes("enabled"), + idpInitiatedSingleLogout: { + enabled: values.get("idpInitiatedSingleLogout").includes("enabled"), + returnToUrls: returnToURLS ? returnToURLS.split(",") : [] }, - nameIdFormat: values.get("nameIdFormat"), - recipients: recipients ? recipients.split(",") : [] + logoutMethod: values.get("logoutMethod"), + logoutRequestUrl: values.get("singleLogoutRequestUrl"), + logoutResponseUrl: values.get("singleLogoutResponseUrl") }, - attributeConsumingServiceIndex: values.get("attributeConsumingServiceIndex"), - bindings: values.get("bindings"), - enableIdpInitiatedSingleSignOn: values.get("idPInitiatedSSO").includes("enableIdPInitiatedSSO"), - enableSignatureValidationForArtifactBinding: - values.get("signatureValidationForArtifactBinding") - .includes("enableSignatureValidationForArtifactBinding") + singleSignOnProfile: { + assertion: { + audiences: audiences ? audiences.split(",") : [], + digestAlgorithm: values.get("digestAlgorithm"), + encryption: { + assertionEncryptionAlgorithm: values.get("assertionEncryptionAlgorithm"), + enabled: values.get("assertionEncryption").includes("enableAssertionEncryption"), + keyEncryptionAlgorithm: values.get("keyEncryptionAlgorithm") + }, + nameIdFormat: values.get("nameIdFormat"), + recipients: recipients ? recipients.split(",") : [] + }, + attributeConsumingServiceIndex: values.get("attributeConsumingServiceIndex"), + bindings: values.get("bindings"), + enableIdpInitiatedSingleSignOn: values.get("idPInitiatedSSO").includes("enableIdPInitiatedSSO"), + enableSignatureValidationForArtifactBinding: + values.get("signatureValidationForArtifactBinding") + .includes("enableSignatureValidationForArtifactBinding") + } } } }; @@ -318,6 +357,27 @@ export const InboundSAMLForm: FunctionComponent = } }; + /** + * Construct the details from the pem value. + */ + const viewCertificate = () => { + if (isPEMSelected && PEMValue) { + const displayCertificate: DisplayCertificate = CertificateManagementUtils.displayCertificate( + null, PEMValue); + + if (displayCertificate) { + setCertificateDisplay(displayCertificate); + setShowCertificateModal(true); + } else { + dispatch(addAlert({ + description: t("console:common.notifications.invalidPEMFile.genericError.description"), + level: AlertLevels.ERROR, + message: t("console:common.notifications.invalidPEMFile.genericError.message") + })); + } + } + }; + return ( metadata ? ( @@ -1425,6 +1485,158 @@ export const InboundSAMLForm: FunctionComponent = /> + + { /* Certificates */ } + + + + + + + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.heading") } + + { + setPEMSelected(values.get("type") === "PEM"); + } + } + type="radio" + value={ certificate?.type } + children={ [ + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.jwks.label"), + value: CertificateTypeInterface.JWKS + }, + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.pem.label"), + value: CertificateTypeInterface.PEM + } + ] } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-type-radio-group` } + /> + + + + + { + isPEMSelected + ? + ( + <> + { + setPEMValue( + values.get("certificateValue") as string + ); + } + } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-textarea` } + /> + < Hint> + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue.hint") + } + + viewCertificate() } + disabled={ isEmpty(PEMValue) } + data-testid={ `${ testId }-certificate-info-button` } + > + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue." + + "actions.view") + } + + + ) + : ( + <> + { + if (!FormValidation.url(value)) { + validation.isValid = false; + validation.errorMessages.push( + t( + "console:develop.features.applications.forms" + + ".advancedConfig.sections.certificate.fields." + + "jwksValue.validations.invalid" + ) + ); + } + } } + value={ + (CertificateTypeInterface.JWKS === certificate?.type) + && certificate?.value + } + readOnly={ readOnly } + data-testid={ `${ testId }-jwks-input` } + /> + + ) + } + + + { + showCertificateModal && ( + { + setShowCertificateModal(false); + } } + /> + ) + } { !readOnly && ( @@ -1436,7 +1648,7 @@ export const InboundSAMLForm: FunctionComponent = className="form-button" data-testid={ `${ testId }-submit-button` } > - { t("common:update")} + { t("common:update") } diff --git a/apps/console/src/features/applications/components/forms/inbound-ws-trust-form.tsx b/apps/console/src/features/applications/components/forms/inbound-ws-trust-form.tsx index 6c679716ceb..33625f7b759 100644 --- a/apps/console/src/features/applications/components/forms/inbound-ws-trust-form.tsx +++ b/apps/console/src/features/applications/components/forms/inbound-ws-trust-form.tsx @@ -16,19 +16,35 @@ * under the License. */ -import { TestableComponentInterface } from "@wso2is/core/models"; -import { Field, Forms } from "@wso2is/forms"; -import { Hint } from "@wso2is/react-components"; +import { AlertInterface, AlertLevels, DisplayCertificate, TestableComponentInterface } from "@wso2is/core/models"; +import { addAlert } from "@wso2is/core/store"; +import { CertificateManagementUtils } from "@wso2is/core/utils"; +import { Field, Forms, Validation } from "@wso2is/forms"; +import { Heading, Hint, LinkButton } from "@wso2is/react-components"; +import { FormValidation } from "@wso2is/validation"; import _ from "lodash"; -import React, { FunctionComponent, ReactElement } from "react"; +import isEmpty from "lodash/isEmpty"; +import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Button, Grid } from "semantic-ui-react"; -import { MetadataPropertyInterface, WSTrustConfigurationInterface, WSTrustMetaDataInterface } from "../../models"; +import { useDispatch } from "react-redux"; +import { Button, Divider, Grid } from "semantic-ui-react"; +import { + CertificateInterface, + CertificateTypeInterface, + MetadataPropertyInterface, + WSTrustConfigurationInterface, + WSTrustMetaDataInterface +} from "../../models"; +import { CertificateFormFieldModal } from "../modals"; /** * Proptypes for the inbound WS Trust form component. */ interface InboundWSTrustFormPropsInterface extends TestableComponentInterface { + /** + * Current certificate configurations. + */ + certificate: CertificateInterface; metadata: WSTrustMetaDataInterface; initialValues: WSTrustConfigurationInterface; onSubmit: (values: any) => void; @@ -50,6 +66,7 @@ export const InboundWSTrustForm: FunctionComponent { const { + certificate, metadata, initialValues, onSubmit, @@ -59,6 +76,25 @@ export const InboundWSTrustForm: FunctionComponent(false); + const [ showCertificateModal, setShowCertificateModal ] = useState(false); + const [ PEMValue, setPEMValue ] = useState(undefined); + const [ certificateDisplay, setCertificateDisplay ] = useState(null); + + /** + * Set initial PEM values. + */ + useEffect(() => { + if (CertificateTypeInterface.PEM === certificate?.type) { + setPEMSelected(true); + if (certificate?.value) { + setPEMValue(certificate.value); + } + } + }, [ certificate ]); + /** * Create drop down options. * @param metadataProp metadata property to create the option. @@ -83,11 +119,42 @@ export const InboundWSTrustForm: FunctionComponent { return { - audience: values.get("audience"), - certificateAlias: values.get("certificateAlias") + general: { + advancedConfigurations: { + certificate: { + type: values.get("type"), + value: isPEMSelected ? values.get("certificateValue") : values.get("jwksValue") + } + } + }, + inbound: { + audience: values.get("audience"), + certificateAlias: values.get("certificateAlias") + } }; }; + /** + * Construct the details from the pem value. + */ + const viewCertificate = () => { + if (isPEMSelected && PEMValue) { + const displayCertificate: DisplayCertificate = CertificateManagementUtils.displayCertificate( + null, PEMValue); + + if (displayCertificate) { + setCertificateDisplay(displayCertificate); + setShowCertificateModal(true); + } else { + dispatch(addAlert({ + description: t("console:common.notifications.invalidPEMFile.genericError.description"), + level: AlertLevels.ERROR, + message: t("console:common.notifications.invalidPEMFile.genericError.message") + })); + } + } + }; + return ( metadata ? ( @@ -154,6 +221,158 @@ export const InboundWSTrustForm: FunctionComponent + + { /* Certificates */ } + + + + + + + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.heading") } + + { + setPEMSelected(values.get("type") === "PEM"); + } + } + type="radio" + value={ certificate?.type } + children={ [ + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.jwks.label"), + value: CertificateTypeInterface.JWKS + }, + { + label: t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.type.children.pem.label"), + value: CertificateTypeInterface.PEM + } + ] } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-type-radio-group` } + /> + + + + + { + isPEMSelected + ? + ( + <> + { + setPEMValue( + values.get("certificateValue") as string + ); + } + } + readOnly={ readOnly } + data-testid={ `${ testId }-certificate-textarea` } + /> + < Hint> + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue.hint") + } + + viewCertificate() } + disabled={ isEmpty(PEMValue) } + data-testid={ `${ testId }-certificate-info-button` } + > + { + t("console:develop.features.applications.forms." + + "advancedConfig.sections.certificate.fields.pemValue." + + "actions.view") + } + + + ) + : ( + <> + { + if (!FormValidation.url(value)) { + validation.isValid = false; + validation.errorMessages.push( + t( + "console:develop.features.applications.forms" + + ".advancedConfig.sections.certificate.fields." + + "jwksValue.validations.invalid" + ) + ); + } + } } + value={ + (CertificateTypeInterface.JWKS === certificate?.type) + && certificate?.value + } + readOnly={ readOnly } + data-testid={ `${ testId }-jwks-input` } + /> + + ) + } + + + { + showCertificateModal && ( + { + setShowCertificateModal(false); + } } + /> + ) + } { !readOnly && ( diff --git a/apps/console/src/features/applications/components/modals/certificate-form-field-modal.tsx b/apps/console/src/features/applications/components/modals/certificate-form-field-modal.tsx new file mode 100644 index 00000000000..3792eda0796 --- /dev/null +++ b/apps/console/src/features/applications/components/modals/certificate-form-field-modal.tsx @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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. + */ + +import { DisplayCertificate, TestableComponentInterface } from "@wso2is/core/models"; +import { CertificateManagementUtils } from "@wso2is/core/utils"; +import { + Certificate as CertificateDisplay, + GenericIcon} from "@wso2is/react-components"; +import React, { FunctionComponent, ReactElement } from "react"; +import { useTranslation } from "react-i18next"; +import { Modal, ModalProps } from "semantic-ui-react"; +import { getCertificateIllustrations } from "../../../core/configs"; + +/** + * Proptypes for the certificate form field modal component. + */ +interface CertificateFormFieldModalPropsInterface extends ModalProps, TestableComponentInterface { + /** + * Current certificate configurations. + */ + certificate: DisplayCertificate; +} + +/** + * Certificate form field modal component. + * + * @param {CertificateFormFieldModalPropsInterface} props - Props injected to the component. + * + * @return {React.ReactElement} + */ +export const CertificateFormFieldModal: FunctionComponent = ( + props: CertificateFormFieldModalPropsInterface +): ReactElement => { + + const { + certificate, + [ "data-testid" ]: testId, + ...rest + } = props; + + const { t } = useTranslation(); + + return ( + + +
+ +
+ View Certificate - { + certificate?.alias + ? certificate?.alias + : certificate?.issuerDN && ( + CertificateManagementUtils.searchIssuerDNAlias(certificate?.issuerDN) + ) + } +

+
Serial Number: { certificate?.serialNumber }
+
+
+ + + +
+ ); +}; + +/** + * Default props for the component. + */ +CertificateFormFieldModal.defaultProps = { + "data-testid": "certificate-form-field-modal" +}; diff --git a/apps/console/src/features/applications/components/modals/index.ts b/apps/console/src/features/applications/components/modals/index.ts new file mode 100644 index 00000000000..4606b12e6c0 --- /dev/null +++ b/apps/console/src/features/applications/components/modals/index.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 * from "./certificate-form-field-modal"; diff --git a/apps/console/src/features/applications/components/settings/access-configuration.tsx b/apps/console/src/features/applications/components/settings/access-configuration.tsx index d78583344f2..04ef4825d33 100644 --- a/apps/console/src/features/applications/components/settings/access-configuration.tsx +++ b/apps/console/src/features/applications/components/settings/access-configuration.tsx @@ -28,7 +28,7 @@ import { import { AxiosResponse } from "axios"; import get from "lodash/get"; import sortBy from "lodash/orderBy"; -import React, { FunctionComponent, MouseEvent, ReactElement, useEffect, useState } from "react"; +import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; import { Trans, useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { Card, Grid, Radio, SemanticWIDTHS } from "semantic-ui-react"; @@ -42,11 +42,13 @@ import { getAuthProtocolMetadata, regenerateClientSecret, revokeClientSecret, + updateApplicationDetails, updateAuthProtocolConfig } from "../../api"; import { getInboundProtocolLogos } from "../../configs"; import { ApplicationTemplateListItemInterface, + CertificateInterface, OIDCDataInterface, SupportedAuthProtocolMetaTypes, SupportedAuthProtocolTypes @@ -68,6 +70,10 @@ interface AccessConfigurationPropsInterface extends SBACInterface { + + updateApplicationDetails({ id: appId, ...values.general }) + .then(() => { + handleInboundConfigFormSubmit(values.inbound, selectedProtocol); + + onUpdate(appId); + }) + .catch((error) => { + if (error.response && error.response.data && error.response.data.description) { + dispatch(addAlert({ + description: error.response.data.description, + level: AlertLevels.ERROR, + message: t("console:develop.features.applications.notifications.updateApplication.error" + + ".message") + })); + + return; + } + + dispatch(addAlert({ + description: t("console:develop.features.applications.notifications.updateApplication" + + ".genericError.description"), + level: AlertLevels.ERROR, + message: t("console:develop.features.applications.notifications.updateApplication.genericError" + + ".message") + })); + }); + }; + /** * Regenerate application. */ @@ -436,6 +478,7 @@ export const AccessConfiguration: FunctionComponent handleInboundConfigFormSubmit(values, selectedProtocol) } + onSubmit={ handleSubmit } type={ selectedProtocol as SupportedAuthProtocolTypes } onApplicationRegenerate={ handleApplicationRegenerate } onApplicationRevoke={ handleApplicationRevoke } @@ -462,13 +505,14 @@ export const AccessConfiguration: FunctionComponent handleInboundConfigFormSubmit(values, selectedProtocol) } + onSubmit={ handleSubmit } type={ SupportedAuthProtocolTypes.CUSTOM } readOnly={ !hasRequiredScopes( diff --git a/apps/console/src/features/applications/components/settings/general-application-settings.tsx b/apps/console/src/features/applications/components/settings/general-application-settings.tsx index 790e7f556ec..a89cdf8ed5e 100644 --- a/apps/console/src/features/applications/components/settings/general-application-settings.tsx +++ b/apps/console/src/features/applications/components/settings/general-application-settings.tsx @@ -34,8 +34,7 @@ import { AppState, FeatureConfigInterface, UIConfigInterface } from "../../../co import { deleteApplication, updateApplicationDetails } from "../../api"; import { ApplicationInterface, - ApplicationTemplateListItemInterface, - CertificateInterface + ApplicationTemplateListItemInterface } from "../../models"; import { GeneralDetailsForm } from "../forms"; @@ -89,10 +88,6 @@ interface GeneralApplicationSettingsInterface extends SBACInterface Date: Tue, 12 Jan 2021 23:15:57 +0530 Subject: [PATCH 3/4] Move `integrate-radio` to selection card --- .../src/themes/default/views/card.overrides | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/modules/theme/src/themes/default/views/card.overrides b/modules/theme/src/themes/default/views/card.overrides index 98772b8f411..93e2f6ad3ec 100644 --- a/modules/theme/src/themes/default/views/card.overrides +++ b/modules/theme/src/themes/default/views/card.overrides @@ -357,6 +357,33 @@ box-shadow: none !important; background: transparent; + .integrate-radio { + flex-grow: 0; + width: 80px; + display: flex; + align-items: center; + justify-content: center; + + .ui.radio.checkbox { + margin-top: -5px !important; + } + + .ui.radio.checkbox label::after { + width: 22px !important; + height: 22px !important; + top: 0px !important; + left: -1px !important; + background-color: @primaryColor !important; + } + + .ui.radio.checkbox label::before { + width: 20px !important; + height: 20px !important; + border-color: @nobel !important; + border-width: 2px !important; + } + } + &.card-selected { box-shadow: none !important; background: @galleryGray; @@ -388,6 +415,10 @@ } } } + + &:hover { + background: @galleryGray; + } } } } From 818bf121f701d9d00c479fd0c4aabd108f812a2e Mon Sep 17 00:00:00 2001 From: Brion Mario Date: Tue, 12 Jan 2021 23:21:19 +0530 Subject: [PATCH 4/4] Remove in-necessary divider --- .../features/applications/components/forms/inbound-oidc-form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/console/src/features/applications/components/forms/inbound-oidc-form.tsx b/apps/console/src/features/applications/components/forms/inbound-oidc-form.tsx index c3b82e11db1..58aaf21f868 100644 --- a/apps/console/src/features/applications/components/forms/inbound-oidc-form.tsx +++ b/apps/console/src/features/applications/components/forms/inbound-oidc-form.tsx @@ -1780,7 +1780,6 @@ export const InboundOIDCForm: FunctionComponent = (initialValues?.clientId || initialValues?.clientSecret) && ( - ) }