/* * Copyright OpenSearch Contributors * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0 * * or in the "license" file accompanying this file. This file 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 React, { useState, useEffect } from 'react'; import { EuiButton, EuiPageContentHeader, EuiPageContentHeaderSection, EuiSpacer, EuiTabbedContent, EuiTitle, EuiPageContent, EuiText, EuiLink, EuiFlexGroup, EuiFlexItem, EuiPageBody, EuiInMemoryTable, EuiEmptyPrompt, EuiCallOut, EuiGlobalToastList, EuiHorizontalRule, EuiButtonEmpty, } from '@elastic/eui'; import { difference } from 'lodash'; import { BreadcrumbsPageDependencies } from '../../../types'; import { buildHashUrl, buildUrl } from '../../utils/url-builder'; import { ResourceType, Action, SubAction, RoleMappingDetail, DataObject, ActionGroupItem, RoleIndexPermissionView, RoleTenantPermissionView, } from '../../types'; import { getRoleMappingData, MappedUsersListing, updateRoleMapping, transformRoleMappingData, UserType, } from '../../utils/role-mapping-utils'; import { createUnknownErrorToast, useToastState } from '../../utils/toast-utils'; import { fetchActionGroups } from '../../utils/action-groups-utils'; import { getRoleDetail } from '../../utils/role-detail-utils'; import { ClusterPermissionPanel } from '../role-view/cluster-permission-panel'; import { IndexPermissionPanel } from './index-permission-panel'; import { TenantsPanel } from './tenants-panel'; import { transformRoleIndexPermissions } from '../../utils/index-permission-utils'; import { transformRoleTenantPermissions } from '../../utils/tenant-utils'; import { DocLinks } from '../../constants'; import { useDeleteConfirmState } from '../../utils/delete-confirm-modal-utils'; import { ExternalLink, ExternalLinkButton } from '../../utils/display-utils'; import { showTableStatusMessage } from '../../utils/loading-spinner-utils'; import { useContextMenuState } from '../../utils/context-menu'; import { requestDeleteRoles } from '../../utils/role-list-utils'; import { setCrossPageToast } from '../../utils/storage-utils'; interface RoleViewProps extends BreadcrumbsPageDependencies { roleName: string; prevAction: string; } const mappedUserColumns = [ { field: 'userType', name: 'User type', sortable: true, }, { field: 'userName', name: 'User', sortable: true, truncateText: true, }, ]; export function RoleView(props: RoleViewProps) { const duplicateRoleLink = buildHashUrl(ResourceType.roles, Action.duplicate, props.roleName); const [mappedUsers, setMappedUsers] = React.useState([]); const [errorFlag, setErrorFlag] = React.useState(false); const [selection, setSelection] = useState([]); const [hosts, setHosts] = React.useState([]); const [actionGroupDict, setActionGroupDict] = React.useState>({}); const [roleClusterPermission, setRoleClusterPermission] = useState([]); const [roleIndexPermission, setRoleIndexPermission] = React.useState( [] ); const [roleTenantPermission, setRoleTenantPermission] = React.useState< RoleTenantPermissionView[] >([]); const [toasts, addToast, removeToast] = useToastState(); const [isReserved, setIsReserved] = React.useState(false); const [loading, setLoading] = React.useState(false); const PERMISSIONS_TAB_INDEX = 0; const MAP_USER_TAB_INDEX = 1; React.useEffect(() => { const fetchData = async () => { try { setLoading(true); const originalRoleMapData = await getRoleMappingData(props.coreStart.http, props.roleName); if (originalRoleMapData) { setMappedUsers(transformRoleMappingData(originalRoleMapData)); setHosts(originalRoleMapData.hosts); } const actionGroups = await fetchActionGroups(props.coreStart.http); setActionGroupDict(actionGroups); const roleData = await getRoleDetail(props.coreStart.http, props.roleName); setIsReserved(roleData.reserved); setRoleClusterPermission(roleData.cluster_permissions); setRoleIndexPermission(transformRoleIndexPermissions(roleData.index_permissions)); setRoleTenantPermission(transformRoleTenantPermissions(roleData.tenant_permissions)); } catch (e) { addToast(createUnknownErrorToast('fetchRoleMappingData', 'load data')); console.log(e); setErrorFlag(true); } finally { setLoading(false); } }; fetchData(); }, [addToast, props.coreStart.http, props.roleName, props.prevAction]); const handleRoleMappingDelete = async () => { try { const usersToDelete: string[] = selection.map((r) => r.userName); const internalUsers: string[] = mappedUsers .filter((r) => r.userType === UserType.internal) .map((r) => r.userName); const externalIdentities: string[] = mappedUsers .filter((r) => r.userType === UserType.external) .map((r) => r.userName); const updateObject: RoleMappingDetail = { users: difference(internalUsers, usersToDelete), backend_roles: difference(externalIdentities, usersToDelete), hosts, }; await updateRoleMapping(props.coreStart.http, props.roleName, updateObject); setMappedUsers(difference(mappedUsers, selection)); setSelection([]); } catch (e) { console.log(e); } }; const [showDeleteConfirmModal, deleteConfirmModal] = useDeleteConfirmState( handleRoleMappingDelete, 'mapping(s)' ); const emptyListMessage = ( No user has been mapped to this role} titleSize="s" body={

You can map users or backend roles to this role

} actions={ { window.location.href = buildHashUrl( ResourceType.roles, Action.edit, props.roleName, SubAction.mapuser ); }} > Map users } /> ); const tabs = [ { id: 'permissions', name: 'Permissions', disabled: false, content: ( <> {isReserved && (

Make use of this role by mapping users. You can also{' '} create your own role {' '} or duplicate this one for further customization.

)} ), }, { id: 'users', name: 'Mapped users', disabled: false, content: ( <>

Mapped users ({mappedUsers.length})

You can map two types of users: users and backend roles. A user can have its own backend role and host for an external authentication and authorization. A backend role directly maps to roles through an external authentication system.{' '}
Delete mapping { window.location.href = buildHashUrl( ResourceType.roles, Action.edit, props.roleName, SubAction.mapuser ); }} > Manage mapping
), }, ]; let pageActions; const actionsMenuItems: React.ReactElement[] = [ duplicate , { try { await requestDeleteRoles(props.coreStart.http, [props.roleName]); setCrossPageToast(buildUrl(ResourceType.roles), { id: 'deleteRole', color: 'success', title: props.roleName + ' deleted.', }); window.location.href = buildHashUrl(ResourceType.roles); } catch (e) { addToast(createUnknownErrorToast('deleteRole', 'delete role')); } }} > delete , ]; const [actionsMenu] = useContextMenuState('Actions', {}, actionsMenuItems); if (isReserved) { pageActions = Duplicate role; } else { pageActions = ( {actionsMenu} Edit role ); } return ( <> {props.buildBreadcrumbs(props.roleName)}

{props.roleName}

{pageActions}
{deleteConfirmModal} ); }