diff --git a/.changeset/ninety-ads-teach.md b/.changeset/ninety-ads-teach.md new file mode 100644 index 00000000000..bb42e8b342a --- /dev/null +++ b/.changeset/ninety-ads-teach.md @@ -0,0 +1,6 @@ +--- +"@wso2is/admin.console-settings.v1": patch +"@wso2is/admin.roles.v2": patch +--- + +Bug fixes related to conosle settings in primary organizations for managed deployments diff --git a/features/admin.administrators.v1/constants/users.ts b/features/admin.administrators.v1/constants/users.ts index 405232223e5..5337456432b 100644 --- a/features/admin.administrators.v1/constants/users.ts +++ b/features/admin.administrators.v1/constants/users.ts @@ -64,6 +64,10 @@ export class AdministratorConstants { // Timeout for the debounce function. public static readonly DEBOUNCE_TIMEOUT: number = 1000; + public static readonly INVITE_INTERNAL_USER_FORM_ID: string = "inviteInternalUserForm"; + + public static readonly INVITE_EXTERNAL_USER_FORM_ID: string = "inviteExternalUserForm"; + /** * Get the consumer users paths as a map. * diff --git a/features/admin.administrators.v1/wizard/steps/admin-user-basic.tsx b/features/admin.administrators.v1/wizard/steps/admin-user-basic.tsx index db30a253440..434d6208af9 100644 --- a/features/admin.administrators.v1/wizard/steps/admin-user-basic.tsx +++ b/features/admin.administrators.v1/wizard/steps/admin-user-basic.tsx @@ -16,9 +16,11 @@ * under the License. */ +import { FeatureAccessConfigInterface } from "@wso2is/access-control"; import { useApplicationList } from "@wso2is/admin.applications.v1/api/application"; import { ApplicationManagementConstants } from "@wso2is/admin.applications.v1/constants"; import { + AppState, SharedUserStoreUtils, UIConstants, UserBasicInterface, @@ -33,7 +35,9 @@ import { UserInviteInterface, UserListInterface } from "@wso2is/admin.users.v1/m import { UserManagementUtils } from "@wso2is/admin.users.v1/utils"; import { getUserNameWithoutDomain } from "@wso2is/core/helpers"; import { IdentifiableComponentInterface, RolesInterface } from "@wso2is/core/models"; -import { Field, FormValue, Forms, Validation } from "@wso2is/forms"; +import { FinalForm, FinalFormField, FormRenderProps, TextFieldAdapter } from "@wso2is/form"; +import { AutocompleteFieldAdapter } from "@wso2is/form/src"; +import { Validation } from "@wso2is/forms"; import { ContentLoader, DocumentationLink, @@ -51,7 +55,9 @@ import isEmpty from "lodash-es/isEmpty"; import kebabCase from "lodash-es/kebabCase"; import React, { FormEvent, ReactElement, useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; import { Divider, DropdownProps, Grid, Header } from "semantic-ui-react"; +import { AdministratorConstants } from "../../constants"; import { InternalAdminFormDataInterface } from "../../models"; import { isAdminUser, isCollaboratorUser } from "../../utils/administrators"; @@ -95,6 +101,12 @@ export const AddAdminUserBasic: React.FunctionComponent const { t } = useTranslation(); const { getLink } = useDocumentation(); + const consoleSettingsFeatureConfig: FeatureAccessConfigInterface = + useSelector((state: AppState) => state?.config?.ui?.features?.consoleSettings); + const isInvitedAdminInConsoleSettingsEnabled: boolean = !consoleSettingsFeatureConfig?.disabledFeatures?.includes( + "consoleSettings.invitedExternalAdmins" + ); + /** * Retrieve the application data for the console application, filtering by name. */ @@ -150,16 +162,29 @@ export const AddAdminUserBasic: React.FunctionComponent .then((response: AxiosResponse) => { setRolesList(response.data.Resources); response.data.Resources.map((role: RolesInterface, index: number) => { - if ((role.meta?.systemRole) && - role.displayName !== "system" && - role.displayName !== "everyone" && - role.displayName !== "selfsignup" && - (role.displayName?.split("/")?.length < 2 && - role.displayName?.split("/")[0] !== "Application") + if(role.displayName === "system" || + role.displayName === "everyone" || + role.displayName === "selfsignup" + ) { + return; + } + + if ( + role.displayName?.split("/")?.length < 2 && + role.displayName?.split("/")[0] === "Application" ) { + return; + } + + if (isInvitedAdminInConsoleSettingsEnabled || + ( + !isInvitedAdminInConsoleSettingsEnabled && role.meta?.systemRole + ) + + ){ roleOptions?.push({ key: index, - text: role?.displayName, + label: role?.displayName, value: role?.displayName }); } @@ -191,6 +216,15 @@ export const AddAdminUserBasic: React.FunctionComponent } }, [ checkedAssignedListItems ]); + useEffect(() => { + document + ?.getElementById( + administratorType === AdminAccountTypes.INTERNAL + ? AdministratorConstants.INVITE_INTERNAL_USER_FORM_ID + : AdministratorConstants.INVITE_EXTERNAL_USER_FORM_ID + )?.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true })); + }, [ triggerSubmit ]); + /** * The following method accepts a Map and returns the values as a string. * @@ -238,14 +272,14 @@ export const AddAdminUserBasic: React.FunctionComponent }); }; - const getFormValues = (values: Map): void => { + const getFormValues = (values: {email: string, roles: DropdownProps[]}): void => { eventPublisher.publish("manage-users-collaborator-role", { - type: kebabCase(values.get("role").toString()) + type: kebabCase(values.roles.toString()) }); const inviteUser: UserInviteInterface = { - email: values.get("email").toString(), - roles: [ values.get("role").toString() ] + email: values.email.toString(), + roles: values.roles.map((role: DropdownProps) => role.value as string) }; if (triggerSubmit) { @@ -308,170 +342,200 @@ export const AddAdminUserBasic: React.FunctionComponent * The modal to add new user internally. */ const inviteInternalUserForm = () => ( - processInternalAdminFormData(administratorConfig.adminRoleName) } - submitState={ triggerSubmit } - > + <> { t("extensions:manage.users.wizard.addAdmin.internal.selectUser") } - - { t("extensions:manage.users.wizard.addAdmin.internal.hint") } - - , - { value }: { value: string; }) => { - handleSearchFieldChange(e, value); - } } - data-componentid={ `${ componentId }-transfer-component` } - > - - { - usersList?.map((user: UserBasicInterface, index: number) => { - - const header: string = getUserNameWithoutDomain(user?.userName); - const subHeader: string = UserManagementUtils.resolveUserListSubheader(user); + { t("extensions:manage.users.wizard.addAdmin.internal.hint") } + processInternalAdminFormData(administratorConfig.adminRoleName) } + render={ ({ handleSubmit }: FormRenderProps) => { + return (
+ , { value }: { value: string }) => { + handleSearchFieldChange(e, value); + } + } + data-componentid={ `${componentId}-transfer-component` } + > + + { usersList?.map((user: UserBasicInterface, index: number) => { + const header: string = getUserNameWithoutDomain(user?.userName); + const subHeader: string = UserManagementUtils.resolveUserListSubheader(user); + + return ( + handleAssignedItemCheckboxChange(user) } + key={ index } + listItem={ { + listItemElement: resolveListItemElement(header), + listItemValue: subHeader + } } + listItemId={ user.id } + listItemIndex={ index } + isItemChecked={ checkedAssignedListItems.some( + (item: UserBasicInterface) => item.id === user.id + ) } + showSecondaryActions={ false } + showListSubItem={ true } + listSubItem={ + header !== subHeader && ( +
+ + + { subHeader } + + +
+ ) + } + data-componentid={ `${componentId}-unselected-transfer-list-item-${index}` } + /> + ); + }) } +
+
+
); - return ( - handleAssignedItemCheckboxChange(user) } - key={ index } - listItem={ { - listItemElement: resolveListItemElement(header), - listItemValue: subHeader - } } - listItemId={ user.id } - listItemIndex={ index } - isItemChecked={ checkedAssignedListItems.some((item: UserBasicInterface) => - item.id === user.id) } - showSecondaryActions={ false } - showListSubItem={ true } - listSubItem={ header !== subHeader && ( -
- - - { subHeader } - - -
- ) } - data-componentid={ `${ componentId }-unselected-transfer-list-item-${ index }` } - /> - ); - } ) - } -
-
-
+ } + } + > + ); /** * The modal to add new user externally. */ const inviteExternalUserForm = () => ( - ) => { - onSubmit(getFormValues(values)); - } } - submitState={ triggerSubmit } - > + <> + { (isUserRoleOptionsRequestLoading || - !userRoleOptions || - userRoleOptions?.length <= 0 - ) ? ( - - ) : ( - - - - { - // Check whether username is a valid email. - // check username validity against userstore regex - if (value && (!FormValidation.email(value) || !SharedUserStoreUtils - .validateInputAgainstRegEx(value, window["AppUtils"] - .getConfig().extensions.collaboratorUsernameRegex))) { - validation.isValid = false; - validation.errorMessages.push(USERNAME_REGEX_VIOLATION_ERROR_MESSAGE); - } - } } - type="email" - tabIndex={ 5 } - maxLength={ 50 } - /> - - - - - - - { "Select a role to assign to the user." + - " The access level of the user is determined by the role." } - - { t("extensions:common.learnMore") } - - - - - - ) + !userRoleOptions || + userRoleOptions?.length <= 0 + ) ? + : + ( { + onSubmit(getFormValues(values)); + } } + render={ ({ handleSubmit }: FormRenderProps) => { + return ( +
+ + + + { + // Check whether username is a valid email. + // check username validity against userstore regex + if ( + value && + ( + !FormValidation.email(value) || + !SharedUserStoreUtils.validateInputAgainstRegEx( + value, window["AppUtils"] + .getConfig().extensions. + collaboratorUsernameRegex + ) + ) + ) { + validation.isValid = false; + validation.errorMessages.push( + USERNAME_REGEX_VIOLATION_ERROR_MESSAGE + ); + } + } } + type="email" + component={ TextFieldAdapter } + tabIndex={ 5 } + maxLength={ 50 } + /> + + + + + + + { "Select a role to assign to the user." + + " The access level of the user is determined by the role." } + + { t("extensions:common.learnMore") } + + + + + +
+ + ); + } } + />) } -
+ + + + ); return ( diff --git a/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx b/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx index f11dfcc9864..a618b3e9287 100644 --- a/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx +++ b/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx @@ -36,6 +36,7 @@ import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import AdministratorsList from "./administrators-list/administrators-list"; import InvitedAdministratorsList from "./invited-administrators/invited-administrators-list"; @@ -57,6 +58,7 @@ const ConsoleAdministrators: FunctionComponent = const { [ "data-componentid" ]: componentId } = props; const { isFirstLevelOrganization, isSubOrganization } = useGetCurrentOrganizationType(); + const { t } = useTranslation(); const consoleSettingsFeatureConfig: FeatureAccessConfigInterface = useSelector((state: AppState) => state?.config?.ui?.features?.consoleSettings); @@ -71,6 +73,7 @@ const ConsoleAdministrators: FunctionComponent = const [ isEnterpriseLoginEnabled, setIsEnterpriseLoginEnabled ] = useState(false); const organizationName: string = store.getState().auth.tenantDomain; + const productName: string = useSelector((state: AppState) => state.config.ui.productName); const useOrgConfig: UseOrganizationConfigType = useOrganizationConfigV2; @@ -186,8 +189,12 @@ const ConsoleAdministrators: FunctionComponent = value={ activeAdministratorGroup } onChange={ (_: ChangeEvent, value: string) => setActiveAdministratorGroup(value) } > - } label="Administrators" /> - } label="Privileged Users" /> + } label={ productName } /> + } + label={ t("common:organizationName", { orgName: organizationName }) } + /> ); } diff --git a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss index c4772fc655a..43dbbc3d7d7 100644 --- a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss +++ b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss @@ -6,4 +6,9 @@ .submit-button { margin-top: 2rem; } + + .multi-option-radio-group { + gap: 40px; + margin: 20px 0; + } } diff --git a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx index d96315bb968..58897370769 100644 --- a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx +++ b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx @@ -16,6 +16,9 @@ * under the License. */ +import FormControlLabel from "@oxygen-ui/react/FormControlLabel"; +import Radio from "@oxygen-ui/react/Radio"; +import RadioGroup from "@oxygen-ui/react/RadioGroup"; import { FeatureStatus, useCheckFeatureStatus, useRequiredScopes } from "@wso2is/access-control"; import { useOrganizationConfigV2 } from "@wso2is/admin.administrators.v1/api/useOrganizationConfigV2"; import { UseOrganizationConfigType } from "@wso2is/admin.administrators.v1/models"; @@ -30,7 +33,7 @@ import { RoleAudienceTypes } from "@wso2is/admin.roles.v2/constants/role-constan import { RoleConstants } from "@wso2is/core/constants"; import { FeatureAccessConfigInterface, IdentifiableComponentInterface, RolesInterface } from "@wso2is/core/models"; import { ResourceTab, ResourceTabPaneInterface } from "@wso2is/react-components"; -import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; +import React, { ChangeEvent, FunctionComponent, ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import ConsoleRolePermissions from "./console-role-permissions"; @@ -64,40 +67,42 @@ interface ConsoleRolesEditPropsInterface extends IdentifiableComponentInterface * @param props - contains role details to be edited. */ const ConsoleRolesEdit: FunctionComponent = ( - props: ConsoleRolesEditPropsInterface): ReactElement => { - - const { - isLoading, - roleObject, - onRoleUpdate, - defaultActiveIndex - } = props; + props: ConsoleRolesEditPropsInterface +): ReactElement => { + const { isLoading, roleObject, onRoleUpdate, defaultActiveIndex } = props; const { t } = useTranslation(); const { isFirstLevelOrganization, isSubOrganization, organizationType } = useGetCurrentOrganizationType(); const userRolesFeatureConfig: FeatureAccessConfigInterface = useSelector( - (state: AppState) => state?.config?.ui?.features?.userRoles); + (state: AppState) => state?.config?.ui?.features?.userRoles + ); const hasRolesUpdatePermissions: boolean = useRequiredScopes(userRolesFeatureConfig?.scopes?.update); const administratorRoleDisplayName: string = useSelector( - (state: AppState) => state?.config?.ui?.administratorRoleDisplayName); + (state: AppState) => state?.config?.ui?.administratorRoleDisplayName + ); - const consoleSettingsFeatureConfig: FeatureAccessConfigInterface = - useSelector((state: AppState) => state?.config?.ui?.features?.consoleSettings); + const consoleSettingsFeatureConfig: FeatureAccessConfigInterface = useSelector( + (state: AppState) => state?.config?.ui?.features?.consoleSettings + ); const isConsoleRolesEditable: boolean = !consoleSettingsFeatureConfig?.disabledFeatures?.includes( "consoleSettings.editableConsoleRoles" ); + const isPrivilegedUsersInConsoleSettingsEnabled: boolean = + !consoleSettingsFeatureConfig?.disabledFeatures?.includes( + "consoleSettings.privilegedUsers" + ); const [ isAdminRole, setIsAdminRole ] = useState(false); const [ isEnterpriseLoginEnabled, setIsEnterpriseLoginEnabled ] = useState(false); + const [ activeUserStore, setActiveUserStore ] = useState("PRIMARY"); const organizationName: string = store.getState().auth.tenantDomain; const useOrgConfig: UseOrganizationConfigType = useOrganizationConfigV2; - const saasFeatureStatus : FeatureStatus = useCheckFeatureStatus( - FeatureGateConstants.SAAS_FEATURES_IDENTIFIER); + const saasFeatureStatus: FeatureStatus = useCheckFeatureStatus(FeatureGateConstants.SAAS_FEATURES_IDENTIFIER); const { data: OrganizationConfig, @@ -121,10 +126,12 @@ const ConsoleRolesEdit: FunctionComponent = ( * Set the if the role is `Internal/admin`. */ useEffect(() => { - if(roleObject) { - setIsAdminRole(roleObject.displayName === RoleConstants.ADMIN_ROLE || - roleObject?.displayName === RoleConstants.ADMIN_GROUP || - roleObject?.displayName === administratorRoleDisplayName); + if (roleObject) { + setIsAdminRole( + roleObject.displayName === RoleConstants.ADMIN_ROLE || + roleObject?.displayName === RoleConstants.ADMIN_GROUP || + roleObject?.displayName === administratorRoleDisplayName + ); } }, [ roleObject ]); @@ -135,8 +142,9 @@ const ConsoleRolesEdit: FunctionComponent = ( render: () => ( = ( = ( ) }, - ( (isFirstLevelOrganization() && isEnterpriseLoginEnabled) || isSubOrganization() ) && { + ((isFirstLevelOrganization() && isEnterpriseLoginEnabled) || isSubOrganization()) && { menuItem: t("roles:edit.menuItems.groups"), render: () => ( @@ -181,10 +186,34 @@ const ConsoleRolesEdit: FunctionComponent = ( menuItem: t("roles:edit.menuItems.users"), render: () => ( + { isFirstLevelOrganization() && + isEnterpriseLoginEnabled && + isPrivilegedUsersInConsoleSettingsEnabled && ( + , value: string) => { + setActiveUserStore(value); + } } + > + } label="Asgardeo" /> + } + label={ organizationName + " organization" } + /> + + ) } + @@ -211,12 +240,10 @@ const ConsoleRolesEdit: FunctionComponent = ( return panes; }; - return ( - + return ( ); }; diff --git a/features/admin.roles.v2/components/edit-role/edit-role-users.tsx b/features/admin.roles.v2/components/edit-role/edit-role-users.tsx index 356c53b1892..ebe6889fed2 100644 --- a/features/admin.roles.v2/components/edit-role/edit-role-users.tsx +++ b/features/admin.roles.v2/components/edit-role/edit-role-users.tsx @@ -81,6 +81,7 @@ export const RoleUsersList: FunctionComponent = ( onRoleUpdate, isReadOnly, tabIndex, + activeUserStore, [ "data-componentid" ]: componentId } = props; @@ -97,7 +98,8 @@ export const RoleUsersList: FunctionComponent = ( const [ activeOption, setActiveOption ] = useState(undefined); const [ availableUserStores, setAvailableUserStores ] = useState([]); const [ selectedUserStoreDomainName, setSelectedUserStoreDomainName ] = useState( - disabledUserstores.includes(RemoteUserStoreConstants.PRIMARY_USER_STORE_NAME) ? "DEFAULT" : "Primary" + activeUserStore ?? + disabledUserstores.includes(RemoteUserStoreConstants.PRIMARY_USER_STORE_NAME) ? "DEFAULT" : "PRIMARY" ); const [ isPlaceholderVisible, setIsPlaceholderVisible ] = useState(true); const [ selectedUsersFromUserStore, setSelectedUsersFromUserStore ] = useState([]); @@ -125,10 +127,14 @@ export const RoleUsersList: FunctionComponent = ( const isUserBelongToSelectedUserStore = (user: UserBasicInterface, userStoreName: string) => { const userNameChunks: string[] = user.userName.split("/"); - return (userNameChunks.length === 1 && userStoreName === "Primary") + return (userNameChunks.length === 1 && userStoreName === "PRIMARY") || (userNameChunks.length === 2 && userNameChunks[0] === userStoreName.toUpperCase()); }; + useEffect(() => { + setSelectedUserStoreDomainName(activeUserStore); + }, [ activeUserStore ]); + useEffect(() => { if (!role?.users?.length) { return; @@ -303,8 +309,15 @@ export const RoleUsersList: FunctionComponent = ( const removedUsers: RolesMemberInterface[] = role?.users?.filter((user: RolesMemberInterface) => { return selectedUsers?.find( (selectedUser: UserBasicInterface) => selectedUser.id === user.value) === undefined; + }).filter((user: RolesMemberInterface) => { + const userNameChunks: string[] = user.display.split("/"); + + return (userNameChunks.length === 1 && selectedUserStoreDomainName === "PRIMARY") + || (userNameChunks.length === 2 && userNameChunks[0] === selectedUserStoreDomainName.toUpperCase()); }) ?? []; + + const removeOperations: PatchUserRemoveOpInterface[] = removedUsers?.map((user: RolesMemberInterface) => { return ({ "op": "remove", @@ -390,33 +403,39 @@ export const RoleUsersList: FunctionComponent = ( { users && availableUserStores && !isReadOnly && ( - - - ) => { + updateSelectedAllUsers(); + setSelectedUsersFromUserStore([]); + setSelectedUserStoreDomainName( + e.target.value as string + ); + } } - } - data-componentid={ `${ componentId }-user-store-domain-dropdown` } - > - { isUserStoresLoading - ?

{ t("common:loading") }

- : availableUserStores?.map((userstore: UserStoreListItem) => - ( - { userstore.name } - ) - ) - } - -
-
+ data-componentid={ + `${ componentId }-user-store-domain-dropdown` + } + > + { isUserStoresLoading + ?

{ t("common:loading") }

+ : availableUserStores?.map((userstore: UserStoreListItem) => + ( + { userstore.name } + ) + ) + } + + +
+ ) }