From bbb4b2c1cfc854745cf817eb2e4d9c22cecffc0b Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Thu, 26 Sep 2024 10:33:09 +0530 Subject: [PATCH 01/13] Introduce app version to the app data structures. --- .../constants/application-management.ts | 5 +++++ .../models/application.ts | 2 ++ .../utils/application-management-utils.ts | 15 ++++++++++++++ .../admin.connections.v1/models/connection.ts | 1 + features/admin.core.v1/models/common.ts | 20 ++++++++++++++----- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/features/admin.applications.v1/constants/application-management.ts b/features/admin.applications.v1/constants/application-management.ts index b0f10ec3b81..c18564c792a 100644 --- a/features/admin.applications.v1/constants/application-management.ts +++ b/features/admin.applications.v1/constants/application-management.ts @@ -41,6 +41,11 @@ export class ApplicationManagementConstants { public static readonly SYSTEM_APPS: string[] = [ this.CONSOLE_APP_NAME ]; public static readonly DEFAULT_APPS: string[] = [ this.MY_ACCOUNT_APP_NAME ]; + /** + * Application latest version. + */ + public static readonly LATEST_VERSION: string = "v1.0.0"; + /** * Private constructor to avoid object instantiation from outside * the class. diff --git a/features/admin.applications.v1/models/application.ts b/features/admin.applications.v1/models/application.ts index 576dad0656d..138ea6d5fa1 100644 --- a/features/admin.applications.v1/models/application.ts +++ b/features/admin.applications.v1/models/application.ts @@ -36,6 +36,7 @@ export interface ApplicationBasicInterface { id?: string; name: string; description?: string; + applicationVersion?: string; accessUrl?: string; clientId?: string; issuer?: string; @@ -331,6 +332,7 @@ export interface ApplicationTemplateListItemInterface { id: string; name: string; description?: string; + applicationVersion?: string; image?: string; authenticationProtocol?: string; /** diff --git a/features/admin.applications.v1/utils/application-management-utils.ts b/features/admin.applications.v1/utils/application-management-utils.ts index 19e4b6e5237..8cd64baa273 100644 --- a/features/admin.applications.v1/utils/application-management-utils.ts +++ b/features/admin.applications.v1/utils/application-management-utils.ts @@ -104,6 +104,21 @@ export class ApplicationManagementUtils { }); } + public static getIfAppIsOutdated(applicationVersion: string): boolean { + + const appVersionArray: number[] = applicationVersion?.match(/\d+/g).map(Number); + const latestAppVersionArray: number[] = ApplicationManagementConstants.LATEST_VERSION.match(/\d+/g).map(Number); + + if (appVersionArray[0] < latestAppVersionArray[0]) { + return true; + } else if (appVersionArray[1] < latestAppVersionArray[1]) { + return true; + } else if (appVersionArray[2] < latestAppVersionArray[2]) { + return true; + } else { + return false; + } + } /** * Gets the list of available custom inbound protocols list and sets them in the redux store. diff --git a/features/admin.connections.v1/models/connection.ts b/features/admin.connections.v1/models/connection.ts index 93d9a1b0a66..40135fcd096 100644 --- a/features/admin.connections.v1/models/connection.ts +++ b/features/admin.connections.v1/models/connection.ts @@ -337,6 +337,7 @@ export interface ApplicationBasicInterface { id?: string; name: string; description?: string; + applicationVersion?: string; accessUrl?: string; clientId?: string; issuer?: string; diff --git a/features/admin.core.v1/models/common.ts b/features/admin.core.v1/models/common.ts index 1b6819945f4..884a6780b36 100644 --- a/features/admin.core.v1/models/common.ts +++ b/features/admin.core.v1/models/common.ts @@ -1,10 +1,19 @@ /** - * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). * - * This software is the property of WSO2 LLC. and its suppliers, if any. - * Dissemination of any information or reproduction of any material contained - * herein in any form is strictly forbidden, unless permitted by WSO2 expressly. - * You may not alter or remove any copyright or other notice from copies of this content. + * WSO2 LLC. 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 { LinkInterface } from "@wso2is/core/models"; @@ -75,6 +84,7 @@ export interface ApplicationBasicInterface { id?: string; name: string; description?: string; + applicationVersion?: string; accessUrl?: string; clientId?: string; issuer?: string; From f1432a3cb589c930597125bd4ff329eb35c64154 Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Thu, 26 Sep 2024 10:33:39 +0530 Subject: [PATCH 02/13] Define documentation links. --- .../admin.extensions.v1/configs/documentation.ts | 12 ++++++++++++ .../configs/models/documentation.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/features/admin.extensions.v1/configs/documentation.ts b/features/admin.extensions.v1/configs/documentation.ts index 9d8c4d7a2e6..e6ad00ef0f8 100644 --- a/features/admin.extensions.v1/configs/documentation.ts +++ b/features/admin.extensions.v1/configs/documentation.ts @@ -144,6 +144,18 @@ export const getDocumentationLinksExtension = () : DocumentationLinksExtensionIn } } }, + oudatedApplications: { + versions: { + version100: { + change1: { + documentationLink: undefined + }, + change2: { + documentationLink: undefined + } + } + } + }, samlApplication: { advanced: { learnMore: undefined diff --git a/features/admin.extensions.v1/configs/models/documentation.ts b/features/admin.extensions.v1/configs/models/documentation.ts index 2795b0e4b45..97d7e9ce05f 100644 --- a/features/admin.extensions.v1/configs/models/documentation.ts +++ b/features/admin.extensions.v1/configs/models/documentation.ts @@ -137,6 +137,18 @@ interface ApplicationsDocumentationLinksInterface { } } } + oudatedApplications: { + versions: { + version100: { + change1: { + documentationLink: string; + }, + change2: { + documentationLink: string; + } + } + } + }, oidcApplication: { advanced: { learnMore: string; From f84cc6a192cbc315ac8dbfd64666f3288d21131a Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Thu, 26 Sep 2024 10:34:23 +0530 Subject: [PATCH 03/13] Define texts. --- .../src/models/namespaces/applications-ns.ts | 31 +++++++++++++++++++ .../en-US/portals/applications.ts | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/modules/i18n/src/models/namespaces/applications-ns.ts b/modules/i18n/src/models/namespaces/applications-ns.ts index 575a05ba8f5..51004877949 100644 --- a/modules/i18n/src/models/namespaces/applications-ns.ts +++ b/modules/i18n/src/models/namespaces/applications-ns.ts @@ -1377,6 +1377,37 @@ export interface ApplicationsNS { }; }; }; + outdatedApplications: { + heading: string; + alert : { + title: string; + content: string; + viewButton: string; + hideButton: string; + cancelButton: string; + } + label: string; + documentationHint: string; + confirmationModal: { + assertionHint: string; + header: string; + message: string; + content: string; + }, + fields: { + commonInstruction: string; + versions: { + version100: { + change1: { + instruction: string; + }, + change2: { + instruction: string; + } + } + }; + } + }; logoutURLs: { heading: string; fields: { diff --git a/modules/i18n/src/translations/en-US/portals/applications.ts b/modules/i18n/src/translations/en-US/portals/applications.ts index 3795c1d7c6b..1141221ab36 100644 --- a/modules/i18n/src/translations/en-US/portals/applications.ts +++ b/modules/i18n/src/translations/en-US/portals/applications.ts @@ -1643,6 +1643,37 @@ export const applications: ApplicationsNS = { }, heading: "ID Token" }, + outdatedApplications: { + heading: "Legacy Application Tokens", + alert : { + title: "Application is outdated.", + content: "This application is using an outdated behavior of applications.", + viewButton: "View Details", + hideButton: "Hide Details", + cancelButton: "Ignore Once" + }, + label: "outdated", + documentationHint: "More Details", + confirmationModal: { + header: "Have you done the relevant changes?", + message: "Proceeding the action without making relevant change will cause the client application behavior break.", + content: "Confirming the action will,", + assertionHint: "Please confirm your action" + }, + fields: { + commonInstruction: "Following behavioral changes will be applied upon update.", + versions: { + version100: { + change1: { + instruction: "Application access token sub attribute will be client_id generated for an application." + }, + change2: { + instruction: "Introspection response for application access token will not include the username attribute." + } + } + } + } + }, logoutURLs: { fields: { back: { From 202f1af7a7ab4bca0fbb4faa401d40dd5c35e0eb Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Thu, 26 Sep 2024 10:34:49 +0530 Subject: [PATCH 04/13] Add banner and label outdated apps. --- .../components/application-list.tsx | 17 + .../pages/application-edit.scss | 34 ++ .../pages/application-edit.tsx | 317 +++++++++++++++++- .../components/page-header/page-header.tsx | 10 +- .../themes/default/elements/label.overrides | 11 + 5 files changed, 386 insertions(+), 3 deletions(-) diff --git a/features/admin.applications.v1/components/application-list.tsx b/features/admin.applications.v1/components/application-list.tsx index 46dbe39314b..76238f33af8 100644 --- a/features/admin.applications.v1/components/application-list.tsx +++ b/features/admin.applications.v1/components/application-list.tsx @@ -426,6 +426,23 @@ export const ApplicationList: FunctionComponent = ) } + +
+ { ApplicationManagementUtils.getIfAppIsOutdated(app.applicationVersion) && ( + + ) } +
+
{ ApplicationManagementUtils.isChoreoApplication(app) && ( diff --git a/features/admin.applications.v1/pages/application-edit.scss b/features/admin.applications.v1/pages/application-edit.scss index 536ea9b575a..2024c172d81 100644 --- a/features/admin.applications.v1/pages/application-edit.scss +++ b/features/admin.applications.v1/pages/application-edit.scss @@ -19,3 +19,37 @@ .application-branding-link { cursor: pointer; } + +.ignore-once-button { + color: #788997; +} + +.banner-detail-card { + border: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + background: #fffaf3; + padding: 5px; + padding-left: 35px; +} + +.banner-detail-content { + padding: 0; +} + +.banner-grid { + padding-left: 30px; +} + +.application-outdated-alert-expanded-view { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.spaced-list li { + margin-bottom: 10px; +} + +.banner-wrapper { + margin-bottom: 20px; +} diff --git a/features/admin.applications.v1/pages/application-edit.tsx b/features/admin.applications.v1/pages/application-edit.tsx index a0a05b01fcc..aa291126de1 100755 --- a/features/admin.applications.v1/pages/application-edit.tsx +++ b/features/admin.applications.v1/pages/application-edit.tsx @@ -16,6 +16,17 @@ * under the License. */ +import Alert from "@oxygen-ui/react/Alert"; +import AlertTitle from "@oxygen-ui/react/AlertTitle"; +import Button from "@oxygen-ui/react/Button"; +import Card from "@oxygen-ui/react/Card"; +import CardContent from "@oxygen-ui/react/CardContent"; +import Grid from "@oxygen-ui/react/Grid"; +import List from "@oxygen-ui/react/List"; +import ListItem from "@oxygen-ui/react/ListItem"; +import ListItemIcon from "@oxygen-ui/react/ListItemIcon"; +import ListItemText from "@oxygen-ui/react/ListItemText"; +import { CheckIcon } from "@oxygen-ui/react-icons"; import { useRequiredScopes } from "@wso2is/access-control"; import ApplicationTemplateMetadataProvider from "@wso2is/admin.application-templates.v1/provider/application-template-metadata-provider"; @@ -33,20 +44,27 @@ import { ExtensionTemplateListInterface } from "@wso2is/admin.template-core.v1/m import { isFeatureEnabled } from "@wso2is/core/helpers"; import { AlertLevels, IdentifiableComponentInterface } from "@wso2is/core/models"; import { addAlert } from "@wso2is/core/store"; +import { Forms } from "@wso2is/forms"; import { AnimatedAvatar, AppAvatar, + ConfirmationModal, + DocumentationLink, LabelWithPopup, Popup, - TabPageLayout + TabPageLayout, + useDocumentation } from "@wso2is/react-components"; +import { AxiosError, AxiosResponse } from "axios"; +import classNames from "classnames"; import cloneDeep from "lodash-es/cloneDeep"; import React, { FunctionComponent, ReactElement, useEffect, useMemo, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; import { RouteComponentProps } from "react-router"; import { Dispatch } from "redux"; import { Label } from "semantic-ui-react"; +import { updateApplicationDetails } from "../api/application"; import { useGetApplication } from "../api/use-get-application"; import { EditApplication } from "../components/edit-application"; import { InboundProtocolDefaultFallbackTemplates } from "../components/meta/inbound-protocols.meta"; @@ -93,6 +111,8 @@ const ApplicationEditPage: FunctionComponent = ( const dispatch: Dispatch = useDispatch(); + const { getLink } = useDocumentation(); + const appDescElement: React.MutableRefObject = useRef(null); const { @@ -129,6 +149,30 @@ const ApplicationEditPage: FunctionComponent = ( error: applicationGetRequestError } = useGetApplication(applicationId, !!applicationId); + const [ viewBannerDetails, setViewBannerDetails ] = useState(false); + const [ displayBanner, setDisplayBanner ] = useState(false); + const [ bannerUpdateLoading, setBannerUpdateLoading ] = useState(false); + const [ showConfirmationModal, setShowConfirmationModal ] = useState(false); + const [ formData, setFormdata ] = useState(undefined); + + useEffect(() => { + if (application != undefined) { + const applicationVersionArray: number[] = application?.applicationVersion?.match(/\d+/g).map(Number); + const latestApplicationVersionArray: number[] = ApplicationManagementConstants.LATEST_VERSION + .match(/\d+/g).map(Number); + + if (applicationVersionArray[0] < latestApplicationVersionArray[0]) { + setDisplayBanner(true); + } else if (applicationVersionArray[1] < latestApplicationVersionArray[1]) { + setDisplayBanner(true); + } else if (applicationVersionArray[2] < latestApplicationVersionArray[2]) { + setDisplayBanner(true); + } else { + setDisplayBanner(false); + } + } + }, [ application ]); + /** * Load the template that the application is built on. */ @@ -506,6 +550,259 @@ const ApplicationEditPage: FunctionComponent = ( return null; }; + /** + * Resolves the application banner content. + * + * @returns Alert banner. + */ + const resolveAlertBanner = (): ReactElement => { + const classes: any = classNames( { "application-outdated-alert-expanded-view": viewBannerDetails } ); + + return ( + displayBanner && + ( +
+ + + + + ) + } + > + + } } > + { t("applications:forms.inboundOIDC.sections.outdatedApplications" + + ".alert.title") } + + + + { t("applications:forms.inboundOIDC.sections.outdatedApplications" + + ".alert.content") } + + + +
+ ) + ); + }; + + /** + * Resolves the application banner view details section. + * + * @returns Alert banner details. + */ + const resolveBannerViewDetails = (): ReactElement => { + return ( + + + + + + + + + + + + + + + + + + + + + + + { showConfirmationModal && confirmationModal() } + + ); + }; + + /** + * Resolves the update confirmation modal. + * + * @returns Confirmation modal. + */ + const confirmationModal = () => { + return ( + setShowConfirmationModal(false) } + type="negative" + open={ showConfirmationModal } + assertionHint={ t("applications:forms.inboundOIDC.sections.outdatedApplications" + + ".confirmationModal.assertionHint") } + assertionType="checkbox" + primaryAction="Confirm" + secondaryAction="Cancel" + onSecondaryActionClick={ (): void =>{ + setShowConfirmationModal(false); + } } + onPrimaryActionClick={ handleBannerCheckBoxUpdateConfirmation } + closeOnDimmerClick={ false } + > + + { t("applications:forms.inboundOIDC.sections.outdatedApplications" + + ".confirmationModal.header") } + + + { t("applications:forms.inboundOIDC.sections.outdatedApplications" + + ".confirmationModal.message") } + + + { t("applications:forms.inboundOIDC.sections.outdatedApplications" + + ".confirmationModal.content") } + + + ); + }; + + /** + * Handles banner content update action which prepares data. + */ + const handleBannerCheckBoxUpdate = () => { + + mutateApplicationGetRequest().then((response: AxiosResponse) => { + const values: ApplicationInterface = { + applicationVersion: ApplicationManagementConstants.LATEST_VERSION, + id: response.data?.id, + name: response.data?.name + }; + + setFormdata({ ...values }); + setShowConfirmationModal(true); + }); + }; + + /** + * Handles the banner data update action. + */ + const handleBannerCheckBoxUpdateConfirmation = async (): Promise => { + setBannerUpdateLoading(true); + + return updateApplicationDetails(formData) + .then(() => { + dispatch(addAlert({ + description: t("applications:notifications.updateApplication.success" + + ".description"), + level: AlertLevels.SUCCESS, + message: t("applications:notifications.updateApplication.success.message") + })); + setDisplayBanner(false); + setShowConfirmationModal(false); + setViewBannerDetails(false); + }) + .catch((error: AxiosError) => { + if (error.response && error.response.data && error.response.data.description) { + dispatch(addAlert({ + description: error.response.data.description, + level: AlertLevels.ERROR, + message: t("applications:notifications.updateApplication.error" + + ".message") + })); + + return; + } + + dispatch(addAlert({ + description: t("applications:notifications.updateApplication" + + ".genericError.description"), + level: AlertLevels.ERROR, + message: t("applications:notifications.updateApplication.genericError" + + ".message") + })); + }) + .finally(() => { + setBannerUpdateLoading(false); + mutateApplicationGetRequest(); + }); + }; + return ( = ( ?? (
{ resolveTemplateLabel() } + { + ApplicationManagementUtils.getIfAppIsOutdated("v0.0.0") && ( + + ) + } { ApplicationManagementUtils.isChoreoApplication(moderatedApplicationData) && (
) } > } } > { t("applications:forms.inboundOIDC.sections.outdatedApplications" - + ".alert.title") } + + ".alert.title") } { t("applications:forms.inboundOIDC.sections.outdatedApplications" - + ".alert.content") } + + ".alert.content") } + { + viewBannerDetails && resolveBannerViewDetails() + } - ) ); @@ -623,20 +625,22 @@ const ApplicationEditPage: FunctionComponent = ( onSubmit={ handleBannerCheckBoxUpdate } data-componentId={ `${componentId}-application-outdated-banner-form` }> - + - + + } + /> + = ( listStyleType: "disc" } }> - + + } + /> + = ( @@ -706,7 +712,7 @@ const ApplicationEditPage: FunctionComponent = ( return ( setShowConfirmationModal(false) } type="negative" open={ showConfirmationModal } @@ -722,12 +728,12 @@ const ApplicationEditPage: FunctionComponent = ( closeOnDimmerClick={ false } > + data-componentId={ `${componentId}-application-update-confirmation-modal-header` }> { t("applications:forms.inboundOIDC.sections.outdatedApplications" + ".confirmationModal.header") } @@ -735,7 +741,7 @@ const ApplicationEditPage: FunctionComponent = ( + ".confirmationModal.message") } { t("applications:forms.inboundOIDC.sections.outdatedApplications" + ".confirmationModal.content") } @@ -805,6 +811,10 @@ const ApplicationEditPage: FunctionComponent = ( }); }; + console.log("application: ", application); + console.log("moderatedApplicationData: ", moderatedApplicationData); + + return ( = (
{ resolveTemplateLabel() } { - ApplicationManagementUtils.getIfAppIsOutdated("v0.0.0") && ( + ApplicationManagementUtils.getIfAppIsOutdated( + moderatedApplicationData?.applicationVersion) && (
- -
- { ApplicationManagementUtils.getIfAppIsOutdated(app.applicationVersion) && ( - - ) } -
-
{ ApplicationManagementUtils.isChoreoApplication(app) && ( diff --git a/features/admin.applications.v1/pages/application-edit.tsx b/features/admin.applications.v1/pages/application-edit.tsx index 615e33781d8..b926f35f333 100755 --- a/features/admin.applications.v1/pages/application-edit.tsx +++ b/features/admin.applications.v1/pages/application-edit.tsx @@ -63,6 +63,7 @@ import { Dispatch } from "redux"; import { Icon, Label } from "semantic-ui-react"; import { updateApplicationDetails } from "../api/application"; import { useGetApplication } from "../api/use-get-application"; +import useGetApplicationInboundConfigs from "../api/use-get-application-inbound-configs"; import { EditApplication } from "../components/edit-application"; import { InboundProtocolDefaultFallbackTemplates } from "../components/meta/inbound-protocols.meta"; import { ApplicationManagementConstants } from "../constants"; @@ -73,6 +74,7 @@ import { ApplicationInterface, ApplicationTemplateListItemInterface, State, + SupportedAuthProtocolName, SupportedAuthProtocolTypes, idpInfoTypeInterface } from "../models"; @@ -146,6 +148,10 @@ const ApplicationEditPage: FunctionComponent = ( error: applicationGetRequestError } = useGetApplication(applicationId, !!applicationId); + const { + data: applicationInboundConfigs + } = useGetApplicationInboundConfigs(applicationId, SupportedAuthProtocolName.OIDC, !!applicationId); + const [ viewBannerDetails, setViewBannerDetails ] = useState(false); const [ displayBanner, setDisplayBanner ] = useState(false); const [ bannerUpdateLoading, setBannerUpdateLoading ] = useState(false); @@ -153,22 +159,13 @@ const ApplicationEditPage: FunctionComponent = ( const [ formData, setFormdata ] = useState(undefined); useEffect(() => { - if (application != undefined) { - const applicationVersionArray: number[] = application?.applicationVersion?.match(/\d+/g).map(Number); - const latestApplicationVersionArray: number[] = ApplicationManagementConstants.LATEST_VERSION - .match(/\d+/g).map(Number); - - if (applicationVersionArray[0] < latestApplicationVersionArray[0]) { - setDisplayBanner(true); - } else if (applicationVersionArray[1] < latestApplicationVersionArray[1]) { - setDisplayBanner(true); - } else if (applicationVersionArray[2] < latestApplicationVersionArray[2]) { - setDisplayBanner(true); - } else { - setDisplayBanner(false); - } + if (application != undefined && applicationInboundConfigs != undefined) { + const isAppOutdated: boolean = ApplicationManagementUtils.getIfAppIsOutdated( + application?.applicationVersion, applicationInboundConfigs?.grantTypes); + + setDisplayBanner(isAppOutdated); } - }, [ application ]); + }, [ application, applicationInboundConfigs ]); /** * Load the template that the application is built on. @@ -626,69 +623,79 @@ const ApplicationEditPage: FunctionComponent = ( data-componentId={ `${componentId}-application-outdated-banner-form` }> - - - - + + + - - - + + + - - - - - - - + + + + ) + } + { + applicationInboundConfigs?.grantTypes + .includes(ApplicationManagementConstants.CLIENT_CREDENTIALS_GRANT) && ( + + + + - - + + - + - - - + } + /> + + + + ) + } - + ) } > @@ -636,22 +638,27 @@ const ApplicationEditPage: FunctionComponent = ( + > + The sub attribute of an application access token now + returns the client_id generated for the application, + instead of the userid of the application owner. + @@ -672,23 +679,26 @@ const ApplicationEditPage: FunctionComponent = ( + > + The introspection responses for application access tokens no longer + return the username attribute. + @@ -698,6 +708,14 @@ const ApplicationEditPage: FunctionComponent = ( } + + +