From 79bace0da46dea05f9e8eae9831c824091cc9d67 Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Fri, 16 Aug 2024 08:42:22 +0530 Subject: [PATCH 1/6] Add legacy application token section. --- .../components/forms/inbound-oidc-form.tsx | 138 +++++++++++++++++- .../advance-attribute-settings.test.tsx | 2 + .../models/application-inbound.ts | 2 + .../src/models/namespaces/applications-ns.ts | 13 ++ .../en-US/portals/applications.ts | 18 +++ 5 files changed, 172 insertions(+), 1 deletion(-) diff --git a/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx b/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx index 2193b5c171c..147c1357c0e 100644 --- a/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx +++ b/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx @@ -16,6 +16,8 @@ * under the License. */ +import Alert from "@oxygen-ui/react/Alert"; +import AlertTitle from "@oxygen-ui/react/AlertTitle"; import Box from "@oxygen-ui/react/Box"; import Chip from "@oxygen-ui/react/Chip"; import { AppState, ConfigReducerStateInterface } from "@wso2is/admin.core.v1"; @@ -215,6 +217,9 @@ export const InboundOIDCForm: FunctionComponent = const { isOrganizationManagementEnabled } = useGlobalVariables(); const [ isEncryptionEnabled, setEncryptionEnable ] = useState(false); const [ isPublicClient, setPublicClient ] = useState(false); + const useClientIdAsSubClaimForAppTokens: boolean = initialValues.useClientIdAsSubClaimForAppTokens; + const omitUsernameInIntrospectionRespForAppTokens: boolean + = initialValues.omitUsernameInIntrospectionRespForAppTokens; const [ callBackUrls, setCallBackUrls ] = useState(""); const [ audienceUrls, setAudienceUrls ] = useState(""); const [ showURLError, setShowURLError ] = useState(false); @@ -290,6 +295,8 @@ export const InboundOIDCForm: FunctionComponent = const requestObjectEncryptionMethod: MutableRefObject = useRef(); const subjectToken: MutableRefObject = useRef(); const applicationSubjectTokenExpiryInSeconds: MutableRefObject = useRef(); + const useClientIdAsSubClaimForAppTokensEle: MutableRefObject = useRef(); + const omitUsernameInIntrospectionRespForAppTokensEle: MutableRefObject = useRef(); const [ isSPAApplication, setSPAApplication ] = useState(false); const [ isOIDCWebApplication, setOIDCWebApplication ] = useState(false); @@ -1353,6 +1360,8 @@ export const InboundOIDCForm: FunctionComponent = } inboundConfigFormValues = { ...inboundConfigFormValues, + omitUsernameInIntrospectionRespForAppTokens: + values.get("omitUsernameInIntrospectionRespForAppTokens")?.length > 0, pushAuthorizationRequest: { requirePushAuthorizationRequest: values.get("requirePushAuthorizationRequest")?.length > 0 }, @@ -1366,7 +1375,8 @@ export const InboundOIDCForm: FunctionComponent = subject: { sectorIdentifierUri: initialValues?.subject?.sectorIdentifierUri, subjectType: initialValues?.subject?.subjectType - } + }, + useClientIdAsSubClaimForAppTokens: values.get("useClientIdAsSubClaimForAppTokens")?.length > 0 }; // If the app is not a newly created, add `clientId` & `clientSecret`. @@ -2566,6 +2576,130 @@ export const InboundOIDCForm: FunctionComponent = ) } + { /* Legacy Application Tokens */ } + { + (!omitUsernameInIntrospectionRespForAppTokens + || !useClientIdAsSubClaimForAppTokens) + && ( + + + + + + + { t("applications:forms.inboundOIDC.sections" + + ".legacyApplicationTokens.heading") } + + + + ) + } + { /* Access Token */ } { !isSystemApplication @@ -4377,6 +4511,7 @@ InboundOIDCForm.defaultProps = { }, idToken: undefined, logout: undefined, + omitUsernameInIntrospectionRespForAppTokens: undefined, pkce: { mandatory: false, supportPlainTransformAlgorithm: false @@ -4386,6 +4521,7 @@ InboundOIDCForm.defaultProps = { scopeValidators: [], state: undefined, subjectToken: undefined, + useClientIdAsSubClaimForAppTokens: undefined, validateRequestObjectSignature: undefined } }; diff --git a/features/admin.applications.v1/components/settings/attribute-management/advance-attribute-settings.test.tsx b/features/admin.applications.v1/components/settings/attribute-management/advance-attribute-settings.test.tsx index 2cba74e7fb2..aa4e8885bac 100644 --- a/features/admin.applications.v1/components/settings/attribute-management/advance-attribute-settings.test.tsx +++ b/features/admin.applications.v1/components/settings/attribute-management/advance-attribute-settings.test.tsx @@ -127,6 +127,7 @@ describe("Advance attribute settings in the attributes tab of Application Edit v }, isFAPIApplication: false, logout: {}, + omitUsernameInIntrospectionRespForAppTokens: false, pkce: { mandatory: true, supportPlainTransformAlgorithm: false }, publicClient: true, pushAuthorizationRequest: { requirePushAuthorizationRequest: false }, @@ -139,6 +140,7 @@ describe("Advance attribute settings in the attributes tab of Application Edit v applicationSubjectTokenExpiryInSeconds: 180, enable: false }, + useClientIdAsSubClaimForAppTokens: false, validateRequestObjectSignature: false } } data-testid={ "advanced-attribute-settings-form" } diff --git a/features/admin.applications.v1/models/application-inbound.ts b/features/admin.applications.v1/models/application-inbound.ts index ac4c4f8460d..e5a3a95f77c 100644 --- a/features/admin.applications.v1/models/application-inbound.ts +++ b/features/admin.applications.v1/models/application-inbound.ts @@ -193,6 +193,8 @@ export interface OIDCDataInterface { subject?: SubjectConfigInterface; isFAPIApplication?: boolean; hybridFlow?: HybridFlowConfigurationInterface; + useClientIdAsSubClaimForAppTokens?: boolean; + omitUsernameInIntrospectionRespForAppTokens?: boolean; } /** diff --git a/modules/i18n/src/models/namespaces/applications-ns.ts b/modules/i18n/src/models/namespaces/applications-ns.ts index 575a05ba8f5..ac8acd91883 100644 --- a/modules/i18n/src/models/namespaces/applications-ns.ts +++ b/modules/i18n/src/models/namespaces/applications-ns.ts @@ -1377,6 +1377,19 @@ export interface ApplicationsNS { }; }; }; + legacyApplicationTokens: { + heading: string; + fields: { + useClientIdAsSubClaimForAppTokens: { + label: string, + hint: string + }; + omitUsernameInIntrospectionRespForAppTokens: { + label: string, + hint: 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..d745f5c6504 100644 --- a/modules/i18n/src/translations/en-US/portals/applications.ts +++ b/modules/i18n/src/translations/en-US/portals/applications.ts @@ -1643,6 +1643,24 @@ export const applications: ApplicationsNS = { }, heading: "ID Token" }, + legacyApplicationTokens: { + heading: "Legacy Application Tokens", + fields: { + useClientIdAsSubClaimForAppTokens: { + label: "Set client_id as the sub claim value for Application tokens", + hint: "For application tokens, the sub claim was previosuly set to the " + + "application owner's user_id. However, to support a more industry standard " + + "solution, this value will be changed to the client ID for application tokens." + }, + omitUsernameInIntrospectionRespForAppTokens: { + label: "Omit sending username claim in the Introspection response for Application tokens", + hint: "For access tokens, the previous behavior includes sending the username claim" + + " in the introspection response. However, to support a more industry standard" + + " solution, the introspection response for application tokens will no longer" + + " include the username claim." + } + } + }, logoutURLs: { fields: { back: { From f357ee513e0cf9419beca1e62739a6d93ad9d446 Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Fri, 16 Aug 2024 11:40:32 +0530 Subject: [PATCH 2/6] Add changeset. --- .changeset/ninety-feet-flow.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/ninety-feet-flow.md diff --git a/.changeset/ninety-feet-flow.md b/.changeset/ninety-feet-flow.md new file mode 100644 index 00000000000..24bb8d6d831 --- /dev/null +++ b/.changeset/ninety-feet-flow.md @@ -0,0 +1,6 @@ +--- +"@wso2is/admin.applications.v1": patch +"@wso2is/i18n": patch +--- + +Introduce UI for legacy app token section. From 9c4a9ac5e1f38106e206364c7e95991562440d63 Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Fri, 16 Aug 2024 14:04:29 +0530 Subject: [PATCH 3/6] Address PR comments. --- .../components/forms/inbound-oidc-form.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx b/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx index 147c1357c0e..d696334e7b1 100644 --- a/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx +++ b/features/admin.applications.v1/components/forms/inbound-oidc-form.tsx @@ -295,8 +295,8 @@ export const InboundOIDCForm: FunctionComponent = const requestObjectEncryptionMethod: MutableRefObject = useRef(); const subjectToken: MutableRefObject = useRef(); const applicationSubjectTokenExpiryInSeconds: MutableRefObject = useRef(); - const useClientIdAsSubClaimForAppTokensEle: MutableRefObject = useRef(); - const omitUsernameInIntrospectionRespForAppTokensEle: MutableRefObject = useRef(); + const useClientIdAsSubClaimForAppTokensElement: MutableRefObject = useRef(); + const omitUsernameInIntrospectionRespForAppTokensElement: MutableRefObject = useRef(); const [ isSPAApplication, setSPAApplication ] = useState(false); const [ isOIDCWebApplication, setOIDCWebApplication ] = useState(false); @@ -2602,19 +2602,19 @@ export const InboundOIDCForm: FunctionComponent = - You currently using an outdated behavior for application tokens. + You are currently using an outdated behavior for application tokens. Please follow the below guideline before migrating to the new behavior.
  1. Client Application Changes: -

    Update your client application to no longer use the - sub claim to refer to the application +

    Update your client application to no longer use the  + sub attribute to refer to the application owner's user ID in the application token.

  2. Introspection Response Updates: -

    Modify your application to stop relying on the - username claim in the introspection endpoint +

    Modify your application to stop relying on the  + username field in the introspection endpoint response for application tokens, as this claim will no longer be included.

  3. @@ -2627,7 +2627,7 @@ export const InboundOIDCForm: FunctionComponent = !useClientIdAsSubClaimForAppTokens && ( <> = ( <> Date: Wed, 21 Aug 2024 16:57:05 +0530 Subject: [PATCH 4/6] Add new pageheader prop. --- .../src/components/page-header/page-header.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/react-components/src/components/page-header/page-header.tsx b/modules/react-components/src/components/page-header/page-header.tsx index 7b446802d8e..a36cf5e8134 100644 --- a/modules/react-components/src/components/page-header/page-header.tsx +++ b/modules/react-components/src/components/page-header/page-header.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * Copyright (c) 2020, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -41,6 +41,10 @@ export interface PageHeaderPropsInterface extends LoadableComponentInterface, Te * Column width for the action container grid. */ actionColumnWidth?: SemanticWIDTHS; + /** + * Alert component displayed in the page header. + */ + alertBanner?: ReactNode; /** * Go back button. */ @@ -127,6 +131,7 @@ export const PageHeader: React.FunctionComponent = ( const { action, actionColumnWidth, + alertBanner, backButton, bottomMargin, className, @@ -301,6 +306,9 @@ export const PageHeader: React.FunctionComponent = ( ) ) } + { + alertBanner + } { action ? ( From 79753a35008447c25694899ae2e201d7201945da Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Mon, 26 Aug 2024 07:25:36 +0530 Subject: [PATCH 5/6] Add i18 components. --- .../src/models/namespaces/applications-ns.ts | 23 +++++++++++++++---- .../en-US/portals/applications.ts | 18 +++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/modules/i18n/src/models/namespaces/applications-ns.ts b/modules/i18n/src/models/namespaces/applications-ns.ts index ac8acd91883..9625aa67087 100644 --- a/modules/i18n/src/models/namespaces/applications-ns.ts +++ b/modules/i18n/src/models/namespaces/applications-ns.ts @@ -1379,14 +1379,29 @@ export interface ApplicationsNS { }; legacyApplicationTokens: { heading: string; + alert : { + title: string; + content: string; + viewButton: string; + cancelButton: string; + } + confirmationModal: { + assertionHint: string; + header: string; + message: string; + content: string; + }, fields: { + commonInstruction: string; useClientIdAsSubClaimForAppTokens: { - label: string, - hint: string + instruction: string; + label: string; + hint: string; }; omitUsernameInIntrospectionRespForAppTokens: { - label: string, - hint: string + instruction: string; + label: string; + hint: string; }; } }; diff --git a/modules/i18n/src/translations/en-US/portals/applications.ts b/modules/i18n/src/translations/en-US/portals/applications.ts index d745f5c6504..53155c2ac96 100644 --- a/modules/i18n/src/translations/en-US/portals/applications.ts +++ b/modules/i18n/src/translations/en-US/portals/applications.ts @@ -1645,14 +1645,32 @@ export const applications: ApplicationsNS = { }, legacyApplicationTokens: { heading: "Legacy Application Tokens", + alert : { + title: "Application is outdated.", + content: "This application is using an outdated behavior for application tokens. " + + "Please follow the below guideline before migrating to the new behavior.", + viewButton: "View Details", + cancelButton: "Ignore Once" + }, + confirmationModal: { + header: "Have you done the relevant changes?", + message: "Proceeding the action without making relevant change will cause the client application behavior break.", + content: "By confirming the action,", + assertionHint: "Please confirm your action" + }, fields: { + commonInstruction: "Change the customer-end applications accordingly to recieve the below updates.", useClientIdAsSubClaimForAppTokens: { + instruction: "Application access token sub attribute will be" + + "client_id generated for an application.", label: "Set client_id as the sub claim value for Application tokens", hint: "For application tokens, the sub claim was previosuly set to the " + "application owner's user_id. However, to support a more industry standard " + "solution, this value will be changed to the client ID for application tokens." }, omitUsernameInIntrospectionRespForAppTokens: { + instruction: "Application access token, Introspection response will not include" + + "the username attribute.", label: "Omit sending username claim in the Introspection response for Application tokens", hint: "For access tokens, the previous behavior includes sending the username claim" + " in the introspection response. However, to support a more industry standard" From 05165c36cbcdd148fff9f0250ebc7619ba86f64e Mon Sep 17 00:00:00 2001 From: Madhavi Gayathri Date: Mon, 26 Aug 2024 07:25:50 +0530 Subject: [PATCH 6/6] Add banner implementation. --- .../pages/application-edit.scss | 16 + .../pages/application-edit.tsx | 407 +++++++++++++++++- 2 files changed, 420 insertions(+), 3 deletions(-) diff --git a/features/admin.applications.v1/pages/application-edit.scss b/features/admin.applications.v1/pages/application-edit.scss index 536ea9b575a..e1580820d43 100644 --- a/features/admin.applications.v1/pages/application-edit.scss +++ b/features/admin.applications.v1/pages/application-edit.scss @@ -19,3 +19,19 @@ .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: #fff; +} + +.application-outdated-alert-expanded-view { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} diff --git a/features/admin.applications.v1/pages/application-edit.tsx b/features/admin.applications.v1/pages/application-edit.tsx index ba0cb6ccdcd..8ebd818e257 100755 --- a/features/admin.applications.v1/pages/application-edit.tsx +++ b/features/admin.applications.v1/pages/application-edit.tsx @@ -16,6 +16,10 @@ * under the License. */ +import Alert from "@oxygen-ui/react/Alert"; +import AlertTitle from "@oxygen-ui/react/AlertTitle"; +import Card from "@oxygen-ui/react/Card"; +import Grid from "@oxygen-ui/react/Grid"; import { useRequiredScopes } from "@wso2is/access-control"; import ApplicationTemplateMetadataProvider from "@wso2is/admin.application-templates.v1/provider/application-template-metadata-provider"; @@ -33,21 +37,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 { Field, Forms } from "@wso2is/forms"; import { AnimatedAvatar, AppAvatar, + ConfirmationModal, + Hint, LabelWithPopup, Popup, TabPageLayout } from "@wso2is/react-components"; +import { AxiosError } from "axios"; import cloneDeep from "lodash-es/cloneDeep"; -import React, { FunctionComponent, ReactElement, useEffect, useMemo, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; +import React, { FunctionComponent, MutableRefObject, ReactElement, useEffect, useMemo, useRef, useState } from "react"; +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 { CardContent, Divider, Label } from "semantic-ui-react"; +import { updateAuthProtocolConfig } 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"; @@ -57,6 +67,7 @@ import { ApplicationAccessTypes, ApplicationInterface, ApplicationTemplateListItemInterface, + OIDCDataInterface, State, SupportedAuthProtocolTypes, idpInfoTypeInterface @@ -64,6 +75,9 @@ import { import { ApplicationManagementUtils } from "../utils/application-management-utils"; import { ApplicationTemplateManagementUtils } from "../utils/application-template-management-utils"; import "./application-edit.scss"; +import { Typography } from "@mui/material"; +import Button from "@oxygen-ui/react/Button"; +import classNames from "classnames"; /** * Prop types for the applications edit page component. @@ -129,6 +143,44 @@ const ApplicationEditPage: FunctionComponent = ( error: applicationGetRequestError } = useGetApplication(applicationId, !!applicationId); + const [ viewBannerDetails, setViewBannerDetails ] = useState(false); + const [ displayBanner, setDisplayBanner ] = useState(false); + const { + data: applicationInboundConfigData, + isLoading: isBannerDataLoading + } = useGetApplicationInboundConfigs(application?.id, SupportedAuthProtocolTypes.OIDC, !!application?.id); + const [ applicationInboundConfig, setApplicationInboundConfig ] = useState(undefined); + const [ useClientIdAsSubClaimForAppTokens, setUseClientIdAsSubClaimForAppTokens ] = useState(false); + const [ omitUsernameInIntrospectionRespForAppTokens, setOmitUsernameInIntrospectionRespForAppTokens ] + = useState(false); + const useClientIdAsSubClaimForAppTokensElement: MutableRefObject = useRef(); + const omitUsernameInIntrospectionRespForAppTokensElement: MutableRefObject = useRef(); + const [ bannerUpdateLoading, setBannerUpdateLoading ] = useState(false); + const [ showConfirmationModal, setShowConfirmationModal ] = useState(false); + const [ formData, setFormdata ] = useState(undefined); + + /** + * Loads banner data. + */ + useEffect(() => { + if (!isBannerDataLoading) { + setApplicationInboundConfig(applicationInboundConfigData); + } + }, [ applicationInboundConfigData, isBannerDataLoading ]); + + /** + * Assign loaded banner data into config states. + */ + useEffect(() => { + if (applicationInboundConfig != undefined) { + setUseClientIdAsSubClaimForAppTokens(applicationInboundConfig.useClientIdAsSubClaimForAppTokens); + setOmitUsernameInIntrospectionRespForAppTokens(applicationInboundConfig + .omitUsernameInIntrospectionRespForAppTokens); + setDisplayBanner(!applicationInboundConfig.useClientIdAsSubClaimForAppTokens + || !applicationInboundConfig.omitUsernameInIntrospectionRespForAppTokens); + } + }, [ applicationInboundConfig ]); + /** * Load the template that the application is built on. */ @@ -506,6 +558,354 @@ 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 ( + !isBannerDataLoading && displayBanner && + ( + <> + { + <> + + + + + ) + } + > + + } } > + { t("applications:forms.inboundOIDC.sections.legacyApplicationTokens" + + ".alert.title") } + + + + { t("applications:forms.inboundOIDC.sections.legacyApplicationTokens" + + ".alert.content") } + + + + + } +