UNPKG

passbolt-styleguide

Version:

Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.

632 lines (589 loc) 21.3 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) Passbolt SA (https://www.passbolt.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 2.13.0 */ import React from "react"; import PropTypes from "prop-types"; import { withAppContext } from "../../../../shared/context/AppContext/AppContext"; import { withUserWorkspace } from "../../../contexts/UserWorkspaceContext"; import EditUser from "../EditUser/EditUser"; import { withDialog } from "../../../contexts/DialogContext"; import DeleteUser from "../DeleteUser/DeleteUser"; import DeleteUserWithConflicts from "../DeleteUser/DeleteUserWithConflicts"; import NotifyError from "../../Common/Error/NotifyError/NotifyError"; import { withActionFeedback } from "../../../contexts/ActionFeedbackContext"; import ConfirmDisableUserMFA from "../ConfirmDisableUserMFA/ConfirmDisableUserMFA"; import { Trans, withTranslation } from "react-i18next"; import HandleReviewAccountRecoveryRequestWorkflow from "../../AccountRecovery/HandleReviewAccountRecoveryRequestWorkflow/HandleReviewAccountRecoveryRequestWorkflow"; import { withWorkflow } from "../../../contexts/WorkflowContext"; import Dropdown from "../../Common/Dropdown/Dropdown"; import DropdownButton from "../../Common/Dropdown/DropdownButton"; import DropdownMenu from "../../Common/Dropdown/DropdownMenu"; import DropdownMenuItem from "../../Common/Dropdown/DropdownMenuItem"; import LinkSVG from "../../../../img/svg/link.svg"; import SendSVG from "../../../../img/svg/send.svg"; import FingerprintDisabledSVG from "../../../../img/svg/fingerprint_disabled.svg"; import BuoySVG from "../../../../img/svg/buoy.svg"; import CloseSVG from "../../../../img/svg/close.svg"; import CopySVG from "../../../../img/svg/copy.svg"; import CaretDownSVG from "../../../../img/svg/caret_down.svg"; import EmailSVG from "../../../../img/svg/email.svg"; import KeySVG from "../../../../img/svg/key.svg"; import DeleteSVG from "../../../../img/svg/delete.svg"; import EditSVG from "../../../../img/svg/edit.svg"; import MetadataKeySVG from "../../../../img/svg/metadata_key.svg"; import RemoveUserSVG from "../../../../img/svg/user_minus.svg"; import ConfirmShareMissingMetadataKeys from "../ConfirmShareMissingMetadataKeys/ConfirmShareMissingMetadataKeys"; import { withClipboard } from "../../../contexts/Clipboard/ManagedClipboardServiceProvider"; import RemoveUserFromGroup from "../../UserGroup/RemoveUserFromGroup/RemoveUserFromGroup"; import MoreHorizontalSVG from "../../../../img/svg/more_horizontal.svg"; import { withRbac } from "../../../../shared/context/Rbac/RbacContext"; import { actions } from "../../../../shared/services/rbacs/actionEnumeration"; /** * This component is a container of multiple actions applicable on user */ class DisplayUserWorkspaceActions extends React.Component { /** * Constructor * @param {Object} props */ constructor(props) { super(props); this.bindCallbacks(); } /** * Bind callbacks methods */ bindCallbacks() { this.handleEditClickEvent = this.handleEditClickEvent.bind(this); this.handleDeleteClickEvent = this.handleDeleteClickEvent.bind(this); this.handleDisableMfaEvent = this.handleDisableMfaEvent.bind(this); this.handleCopyPermalinkEvent = this.handleCopyPermalinkEvent.bind(this); this.handleCopyEmailClickEvent = this.handleCopyEmailClickEvent.bind(this); this.handleCopyPublicKeyEvent = this.handleCopyPublicKeyEvent.bind(this); this.handleResendInviteClickEvent = this.handleResendInviteClickEvent.bind(this); this.handleReviewRecoveryRequestEvent = this.handleReviewRecoveryRequestEvent.bind(this); this.handleClearSelectionClick = this.handleClearSelectionClick.bind(this); this.handleShareMissingMetadataKeysEvent = this.handleShareMissingMetadataKeysEvent.bind(this); this.handleRemoveUserClickEvent = this.handleRemoveUserClickEvent.bind(this); } /** * Returns true if the current user can delete the current selected user * @returns {boolean} */ get canDelete() { return ( this.isLoggedInUserAdmin() && this.selectedUser && this.props.context.loggedInUser.id !== this.selectedUser.id ); } /** * Returns true if the current user can remove the current selected user from group * @returns {boolean} */ get canRemoveFromGroup() { const { filter } = this.props.userWorkspaceContext; const selectedUser = this.selectedUser; // Only show remove option when viewing a specific group's members if (filter.type !== "FILTER-BY-GROUP" || !selectedUser) { return false; } const group = filter.payload.group; // Only admins or group managers can remove members const isAdmin = this.isLoggedInUserAdmin(); if (!isAdmin) { const isManager = group.my_group_user && group.my_group_user.is_admin; if (!isManager) { return false; } } const groupUsers = group.groups_users; // Can't remove the only member of a group if (groupUsers.length === 1) { return false; } // Don't allow removing the last manager from the group const managers = groupUsers.filter((u) => u.is_admin); if (managers.length === 1 && managers[0].user_id === selectedUser.id) { return false; } return true; } /** * Handle the will of copying the user permalink */ handleCopyPermalinkEvent() { this.copyPermalink(); } /** * Handle the will of copying the user email address * @returns {Promise<void>} */ async handleCopyEmailClickEvent() { await this.props.clipboardContext.copy( this.selectedUser.username, this.translate("The email address has been copied to clipboard."), ); } /** * Handle the will of copying the user public key * @returns {Promise<void>} */ async handleCopyPublicKeyEvent() { const gpgKeyInfo = await this.props.context.port.request( "passbolt.keyring.get-public-key-info-by-user", this.selectedUser.id, ); await this.props.clipboardContext.copy( gpgKeyInfo?.armored_key, this.translate("The public key has been copied to clipboard."), ); } /** * Handle the will of */ handleResendInviteClickEvent() { this.resendInvite(); } /** * Handle edit click event */ handleEditClickEvent() { const editUserDialogProps = { id: this.selectedUser.id, }; this.props.context.setContext({ editUserDialogProps }); this.props.dialogContext.open(EditUser); } /** * Handle delete click event * @returns {Promise<void>} */ async handleDeleteClickEvent() { try { await this.props.context.port.request("passbolt.users.delete-dry-run", this.selectedUser.id); this.displayDeleteUserDialog(); } catch (error) { if (error.name === "DeleteDryRunError") { this.displayDeleteUserWithConflictsDialog(error.errors); } else { this.handleError(error); } } } /** * Handle remove user click event * @returns {Promise<void>} */ async handleRemoveUserClickEvent() { const removeUserFromGroupDialogProps = { user: this.selectedUser, group: this.props.userWorkspaceContext.filter.payload.group, }; this.props.context.setContext({ removeUserFromGroupDialogProps }); this.props.dialogContext.open(RemoveUserFromGroup); } /** * Display delete user dialog when there is not conflict to solve */ displayDeleteUserDialog() { const deleteUserDialogProps = { user: this.selectedUser, }; this.props.context.setContext({ deleteUserDialogProps }); this.props.dialogContext.open(DeleteUser); } /** * Display delete user dialog when there is conflict to solve * @param {Object} errors - The errors to display */ displayDeleteUserWithConflictsDialog(errors) { const deleteUserWithConflictsDialogProps = { user: this.selectedUser, errors: errors, }; this.props.context.setContext({ deleteUserWithConflictsDialogProps }); this.props.dialogContext.open(DeleteUserWithConflicts); } /** * Display error dialog * @param {Error} error - The error to display */ handleError(error) { const errorDialogProps = { error: error, }; this.props.dialogContext.open(NotifyError, errorDialogProps); } /** * Handle the will of disable MFA for a user */ handleDisableMfaEvent() { this.disableMFA(); } /** * Handle review recovery request click event */ handleReviewRecoveryRequestEvent() { const accountRecoveryRequestId = this.selectedUser.pending_account_recovery_request.id; this.props.workflowContext.start(HandleReviewAccountRecoveryRequestWorkflow, { accountRecoveryRequestId }); } /** * Handle share missing metadata keys click event */ handleShareMissingMetadataKeysEvent() { const shareMissingMetadataKeysDialogProps = { user: this.selectedUser, }; this.props.dialogContext.open(ConfirmShareMissingMetadataKeys, shareMissingMetadataKeysDialogProps); } /** * Get selected user * @returns {User|null} The selected user or null */ get selectedUser() { return this.props.userWorkspaceContext.selectedUsers[0]; } /** * Returns true if more actions are available * @returns {boolean} */ get hasMoreActionAllowed() { return this.isLoggedInUserAdmin() && this.hasOneUserSelected() && (this.canDelete || this.canIUseMfa); } /** * Check if the logged in user can use the MFA capability * @returns {boolean} */ get canIUseMfa() { return ( this.isLoggedInUserAdmin() && this.hasOneUserSelected() && this.props.context.siteSettings.canIUse("multiFactorAuthentication") && this.selectedUser.is_mfa_enabled ); } /** * Returns true if the logged in user can use the resend capability * @returns {boolean} */ get canIUseResend() { return this.isLoggedInUserAdmin() && !this.isActiveUser; } /** * Returns true if the selected user is active * @returns {boolean} */ get isActiveUser() { return this.selectedUser?.active; } /** * Check if the user can use the review account recovery request capability * @returns {boolean} */ get canIReviewAccountRecoveryRequest() { return ( this.props.rbacContext.canIUseAction(actions.ACCOUNT_RECOVERY_RESPONSE_CREATE) && this.props.rbacContext.canIUseAction(actions.ACCOUNT_RECOVERY_REQUEST_INDEX) && this.hasOneUserSelected() && this.props.context.siteSettings.canIUse("accountRecovery") && Boolean(this.selectedUser.pending_account_recovery_request) ); } /** * Check if the user can use the share missing data key capability * @returns {boolean} */ get canIShareMissingMetadataKeys() { return ( this.isLoggedInUserAdmin() && this.hasOneUserSelected() && this.props.context.siteSettings.canIUse("metadata") && this.selectedUser.missing_metadata_key_ids?.length > 0 && this.props.context.loggedInUser.id !== this.selectedUser.id ); } /** * Check if the users workspace has one user selected * @returns {boolean} */ hasOneUserSelected() { return this.props.userWorkspaceContext.selectedUsers.length === 1; } /** * Check if the logged in user is an admin * @returns {boolean} */ isLoggedInUserAdmin() { return this.props.context.loggedInUser && this.props.context.loggedInUser.role.name === "admin"; } /** * Disable the selected user's MFA */ disableMFA() { this.props.dialogContext.open(ConfirmDisableUserMFA); } /** * Copy the user permalink * @returns {Promise<void>} */ async copyPermalink() { const baseUrl = this.props.context.userSettings.getTrustedDomain(); const permalink = `${baseUrl}/app/users/view/${this.selectedUser.id}`; await this.props.clipboardContext.copy(permalink, this.translate("The permalink has been copied to clipboard.")); } /** * Resend an invite to the given user */ resendInvite() { this.props.context.port .request("passbolt.users.resend-invite", this.selectedUser.username) .then(this.onResendInviteSuccess.bind(this)) .catch(this.handleError.bind(this)); } /** * Whenever the resend invite succeeds */ onResendInviteSuccess() { this.props.actionFeedbackContext.displaySuccess(this.translate("The invite has been resent successfully")); } /** * Handle the event on the 'close' icon to clear the current selection * @returns {Promise<void>} */ async handleClearSelectionClick() { await this.props.userWorkspaceContext.onUserSelected.none(); } /** * Get the translate function * @returns {Function} The translation function */ get translate() { return this.props.t; } /** * Render the component * @returns {JSX.Element} The rendered component */ render() { const count = this.props.userWorkspaceContext.selectedUsers?.length; return ( <div className="actions" ref={this.props.actionsButtonRef}> <div className="actions-wrapper"> <ul> {this.hasOneUserSelected() && ( <> <li id="copy-action"> <Dropdown> <DropdownButton className="button-action-contextual"> <CopySVG /> <span> <Trans>Copy</Trans> </span> <CaretDownSVG /> </DropdownButton> <DropdownMenu className="menu-action-contextual"> <DropdownMenuItem> <button id="copy-user-email" type="button" className="no-border" onClick={this.handleCopyEmailClickEvent} > <EmailSVG /> <span> <Trans>Copy email address</Trans> </span> </button> </DropdownMenuItem> <DropdownMenuItem> <button id="copy-user-permalink" type="button" className="no-border" onClick={this.handleCopyPermalinkEvent} > <LinkSVG /> <span> <Trans>Copy permalink</Trans> </span> </button> </DropdownMenuItem> {this.isActiveUser && ( <DropdownMenuItem> <button id="copy-user-public-key" type="button" className="no-border" onClick={this.handleCopyPublicKeyEvent} > <KeySVG /> <span> <Trans>Copy public key</Trans> </span> </button> </DropdownMenuItem> )} </DropdownMenu> </Dropdown> </li> {this.isLoggedInUserAdmin() && ( <li> <button id="edit-user" type="button" className="button-action-contextual" onClick={this.handleEditClickEvent} > <EditSVG /> <span> <Trans>Edit</Trans> </span> </button> </li> )} </> )} {this.canRemoveFromGroup && ( <li> <button id="remove-user-from-group" type="button" className="button-action-contextual" onClick={this.handleRemoveUserClickEvent} > <RemoveUserSVG /> <span> <Trans>Remove from group</Trans> </span> </button> </li> )} {this.canIUseResend && ( <li> <button id="resend-invite-user" className="button-action-contextual" type="button" onClick={this.handleResendInviteClickEvent} > <SendSVG /> <span> <Trans>Resend invite</Trans> </span> </button> </li> )} {this.canIReviewAccountRecoveryRequest && ( <li> <button id="review-recovery" className="button-action-contextual" type="button" onClick={this.handleReviewRecoveryRequestEvent} > <BuoySVG /> <span> <Trans>Review recovery request</Trans> </span> </button> </li> )} {this.canIShareMissingMetadataKeys && ( <li> <button id="share-metadata-keys" className="button-action-contextual" type="button" onClick={this.handleShareMissingMetadataKeysEvent} > <MetadataKeySVG /> <span> <Trans>Share metadata keys</Trans> </span> </button> </li> )} {this.hasMoreActionAllowed && ( <li> <Dropdown> <DropdownButton className="more button-action-contextual button-action-icon"> <MoreHorizontalSVG /> </DropdownButton> <DropdownMenu className="menu-action-contextual"> {this.canDelete && ( <DropdownMenuItem> <button id="delete-user" type="button" className="no-border" onClick={this.handleDeleteClickEvent} aria-label="Delete user" > <DeleteSVG /> <span> <Trans>Delete</Trans> </span> </button> </DropdownMenuItem> )} {this.canIUseMfa && ( <DropdownMenuItem> <button id="disable-mfa-action" className="no-border" type="button" onClick={this.handleDisableMfaEvent} aria-label="Diable MFA" > <FingerprintDisabledSVG /> <span> <Trans>Disable MFA</Trans> </span> </button> </DropdownMenuItem> )} </DropdownMenu> </Dropdown> </li> )} </ul> <span className="counter"> <Trans count={count}>{{ count }} selected</Trans> </span> <button type="button" className="button-transparent inline" onClick={this.handleClearSelectionClick}> <CloseSVG /> </button> </div> </div> ); } } DisplayUserWorkspaceActions.propTypes = { actionsButtonRef: PropTypes.object, // The forwarded ref of the buttons container context: PropTypes.any, // The application context userWorkspaceContext: PropTypes.any, // the user workspace context workflowContext: PropTypes.any, // the workflow context dialogContext: PropTypes.any, // the dialog context rbacContext: PropTypes.any, // the rbac context actionFeedbackContext: PropTypes.object, // the action feeedback context clipboardContext: PropTypes.object, // the clipboard service t: PropTypes.func, // The translation function }; export default withAppContext( withActionFeedback( withRbac( withWorkflow( withDialog(withUserWorkspace(withClipboard(withTranslation("common")(DisplayUserWorkspaceActions)))), ), ), ), );