diff --git a/frontend/src/components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog.js b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog.js new file mode 100644 index 0000000000..3db367c0ef --- /dev/null +++ b/frontend/src/components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Modal, ModalBody, ModalFooter, Form, FormGroup, Input, Label } from 'reactstrap'; +import { gettext } from '../../../utils/constants'; +import SeahubModalHeader from '@/components/common/seahub-modal-header'; + +const propTypes = { + toggleDialog: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired +}; + +class SysAdminUserDeactivateDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + keepSharing: true + }; + } + + handleOptionChange = (e) => { + this.setState({ keepSharing: e.target.value === 'true' }); + }; + + submit = () => { + this.props.onSubmit(this.state.keepSharing); + this.props.toggleDialog(); + }; + + render() { + return ( + + + {gettext('Set user inactive')} + + +
+ +

{gettext('Do you want to keep the sharing relationships?')}

+ + + + + + +
+
+
+ + + + +
+ ); + } +} + +SysAdminUserDeactivateDialog.propTypes = propTypes; + +export default SysAdminUserDeactivateDialog; diff --git a/frontend/src/components/single-selector.js b/frontend/src/components/single-selector.js index e34fcb97a1..660069fb04 100644 --- a/frontend/src/components/single-selector.js +++ b/frontend/src/components/single-selector.js @@ -56,7 +56,7 @@ class Selector extends Component { selectItem = (e, targetItem) => { e.stopPropagation(); if (this.props.operationBeforeSelect) { - this.props.operationBeforeSelect(); + this.props.operationBeforeSelect(targetItem); } else { this.props.selectOption(targetItem); } diff --git a/frontend/src/pages/sys-admin/orgs/org-users.js b/frontend/src/pages/sys-admin/orgs/org-users.js index 5e63683a72..e8f659b48b 100644 --- a/frontend/src/pages/sys-admin/orgs/org-users.js +++ b/frontend/src/pages/sys-admin/orgs/org-users.js @@ -16,6 +16,7 @@ import OpMenu from '../../../components/dialog/op-menu'; import MainPanelTopbar from '../main-panel-topbar'; import UserLink from '../user-link'; import OrgNav from './org-nav'; +import SysAdminUserDeactivateDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog'; dayjs.extend(relativeTime); @@ -160,7 +161,10 @@ class Item extends Component { this.setState({ isResetPasswordDialogOpen: !this.state.isResetPasswordDialogOpen }); }; - toggleConfirmInactiveDialog = () => { + toggleConfirmInactiveDialog = (targetItem) => { + if (targetItem?.value === 'active') { + return; + } this.setState({ isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen }); }; @@ -168,8 +172,8 @@ class Item extends Component { this.props.updateStatus(this.props.item.email, statusOption.value); }; - setUserInactive = () => { - this.props.updateStatus(this.props.item.email, 'inactive'); + setUserInactive = (keepSharing) => { + this.props.updateStatus(this.props.item.email, 'inactive', { keepSharing: keepSharing }); }; updateMembership = (membershipOption) => { @@ -229,7 +233,6 @@ class Item extends Component { const itemName = '' + Utils.HTMLescape(item.name) + ''; let deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName); let resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName); - const confirmSetUserInactiveMsg = gettext('Are you sure you want to set {user_placeholder} inactive?').replace('{user_placeholder}', itemName); // for 'user status' const curStatus = item.active ? 'active' : 'inactive'; @@ -311,13 +314,10 @@ class Item extends Component { /> } {isConfirmInactiveDialogOpen && - + } ); @@ -396,9 +396,9 @@ class OrgUsers extends Component { }); }; - updateStatus = (email, statusValue) => { + updateStatus = (email, statusValue, options = {}) => { const isActive = statusValue == 'active'; - systemAdminAPI.sysAdminUpdateOrgUser(this.props.orgID, email, 'active', isActive).then(res => { + systemAdminAPI.sysAdminUpdateOrgUser(this.props.orgID, email, 'active', isActive, options).then(res => { let newUserList = this.state.userList.map(item => { if (item.email == email) { item.active = res.data.active; diff --git a/frontend/src/pages/sys-admin/users/search-users.js b/frontend/src/pages/sys-admin/users/search-users.js index 839e2c8d10..c6a9d2d2c4 100644 --- a/frontend/src/pages/sys-admin/users/search-users.js +++ b/frontend/src/pages/sys-admin/users/search-users.js @@ -204,8 +204,8 @@ class SearchUsers extends Component { }); }; - updateUser = (email, key, value) => { - systemAdminAPI.sysAdminUpdateUser(email, key, value).then(res => { + updateUser = (email, key, value, options) => { + systemAdminAPI.sysAdminUpdateUser(email, key, value, options).then(res => { let newUserList = this.state.userList.map(item => { if (item.email == email) { item[key] = res.data[key]; diff --git a/frontend/src/pages/sys-admin/users/users-content.js b/frontend/src/pages/sys-admin/users/users-content.js index b360836492..ac2dc15c5d 100644 --- a/frontend/src/pages/sys-admin/users/users-content.js +++ b/frontend/src/pages/sys-admin/users/users-content.js @@ -15,6 +15,7 @@ import OpMenu from '../../../components/dialog/op-menu'; import SysAdminUserSetQuotaDialog from '../../../components/dialog/sysadmin-dialog/set-quota'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import UserLink from '../user-link'; +import SysAdminUserDeactivateDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-user-deactivate-dialog'; const { availableRoles, availableAdminRoles, institutions } = window.sysadmin.pageOptions; dayjs.extend(relativeTime); @@ -206,7 +207,7 @@ class Item extends Component { isDeleteUserDialogOpen: false, isResetUserPasswordDialogOpen: false, isRevokeAdminDialogOpen: false, - isConfirmInactiveDialogOpen: false + isConfirmInactiveDialogOpen: false, }; } @@ -252,7 +253,10 @@ class Item extends Component { this.setState({ isRevokeAdminDialogOpen: !this.state.isRevokeAdminDialogOpen }); }; - toggleConfirmInactiveDialog = () => { + toggleConfirmInactiveDialog = (targetItem) => { + if (targetItem?.value === 'active') { + return; + } this.setState({ isConfirmInactiveDialogOpen: !this.state.isConfirmInactiveDialogOpen }); }; @@ -268,10 +272,14 @@ class Item extends Component { this.props.updateUser(this.props.item.email, 'is_active', isActive); }; - setUserInactive = () => { - this.props.updateUser(this.props.item.email, 'is_active', false); + setUserInactive = (keepSharing) => { + this.props.updateUser(this.props.item.email, 'is_active', false, { + keep_sharing: keepSharing + }); + this.toggleConfirmInactiveDialog(); }; + updateRole = (roleOption) => { this.props.updateUser(this.props.item.email, 'role', roleOption.value); }; @@ -408,7 +416,6 @@ class Item extends Component { const deleteDialogMsg = gettext('Are you sure you want to delete {placeholder} ?').replace('{placeholder}', itemName); const resetPasswordDialogMsg = gettext('Are you sure you want to reset the password of {placeholder} ?').replace('{placeholder}', itemName); const revokeAdminDialogMsg = gettext('Are you sure you want to revoke the admin permission of {placeholder} ?').replace('{placeholder}', itemName); - const confirmSetUserInactiveMsg = gettext('Are you sure you want to set {user_placeholder} inactive?').replace('{user_placeholder}', itemName); // for 'user status' const curStatus = item.is_active ? 'active' : 'inactive'; @@ -585,12 +592,9 @@ class Item extends Component { /> } {isConfirmInactiveDialogOpen && - } diff --git a/frontend/src/pages/sys-admin/users/users.js b/frontend/src/pages/sys-admin/users/users.js index b63bef46e1..2d74ecd774 100644 --- a/frontend/src/pages/sys-admin/users/users.js +++ b/frontend/src/pages/sys-admin/users/users.js @@ -360,8 +360,8 @@ class Users extends Component { }); }; - updateUser = (email, key, value) => { - systemAdminAPI.sysAdminUpdateUser(email, key, value).then(res => { + updateUser = (email, key, value, options = {}) => { + systemAdminAPI.sysAdminUpdateUser(email, key, value, options).then(res => { let newUserList = this.state.userList.map(item => { if (item.email == email) { item[key] = res.data[key]; diff --git a/frontend/src/utils/system-admin-api.js b/frontend/src/utils/system-admin-api.js index 5757825839..23079f04f9 100644 --- a/frontend/src/utils/system-admin-api.js +++ b/frontend/src/utils/system-admin-api.js @@ -642,12 +642,15 @@ class SystemAdminAPI { return this._sendPostRequest(url, formData); } - sysAdminUpdateOrgUser(orgID, email, attribute, value) { + sysAdminUpdateOrgUser(orgID, email, attribute, value, options = {}) { const url = this.server + '/api/v2.1/admin/organizations/' + orgID + '/users/' + encodeURIComponent(email) + '/'; let formData = new FormData(); switch (attribute) { case 'active': formData.append('active', value); + if (options.keepSharing !== undefined) { + formData.append('keep_sharing', options.keepSharing); + } break; case 'is_org_staff': formData.append('is_org_staff', value); @@ -957,7 +960,7 @@ class SystemAdminAPI { return this._sendPostRequest(url, formData); } - sysAdminUpdateUser(email, attribute, value) { + sysAdminUpdateUser(email, attribute, value, options = {}) { const url = this.server + '/api/v2.1/admin/users/' + encodeURIComponent(email) + '/'; let formData = new FormData(); switch (attribute) { @@ -966,6 +969,9 @@ class SystemAdminAPI { break; case 'is_active': formData.append('is_active', value); + if (options.keep_sharing !== undefined) { + formData.append('keep_sharing', options.keep_sharing); + } break; case 'is_staff': formData.append('is_staff', value); diff --git a/seahub/api2/endpoints/admin/org_users.py b/seahub/api2/endpoints/admin/org_users.py index f959254705..572fbe7a17 100644 --- a/seahub/api2/endpoints/admin/org_users.py +++ b/seahub/api2/endpoints/admin/org_users.py @@ -31,6 +31,8 @@ from seahub.api2.permissions import IsProVersion from seahub.api2.endpoints.utils import is_org_user from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \ datetime_to_isoformat_timestr +from seahub.utils.db_api import SeafileDB +from seahub.share.models import ExtraSharePermission try: from seahub.settings import ORG_MEMBER_QUOTA_ENABLED @@ -303,6 +305,20 @@ class AdminOrgUser(APIView): else: user.is_active = False + keep_sharing = request.data.get("keep_sharing", None) + username = request.user.username + if keep_sharing and keep_sharing == 'false': + seafile_db = SeafileDB() + orgs = ccnet_api.get_orgs_by_user(email) + if orgs: + org_id = orgs[0].org_id + seafile_db.delete_received_share_by_user(email, org_id) + seafile_db.delete_share_by_user(email, org_id) + else: + seafile_db.delete_received_share_by_user(email) + seafile_db.delete_share_by_user(email) + ExtraSharePermission.objects.filter(share_to=username).delete() + try: # update user status result_code = user.save() diff --git a/seahub/api2/endpoints/admin/users.py b/seahub/api2/endpoints/admin/users.py index a818649fbb..7f243f20a7 100644 --- a/seahub/api2/endpoints/admin/users.py +++ b/seahub/api2/endpoints/admin/users.py @@ -66,7 +66,7 @@ from seahub.auth.utils import get_virtual_id_by_email from seahub.auth.models import SocialAuthUser from seahub.options.models import UserOptions -from seahub.share.models import FileShare, UploadLinkShare +from seahub.share.models import FileShare, UploadLinkShare, ExtraSharePermission from seahub.utils.ldap import ENABLE_LDAP, LDAP_FILTER, ENABLE_SASL, SASL_MECHANISM, ENABLE_SSO_USER_CHANGE_PASSWORD, \ LDAP_PROVIDER, LDAP_SERVER_URL, LDAP_BASE_DN, LDAP_ADMIN_DN, LDAP_ADMIN_PASSWORD, LDAP_LOGIN_ATTR, LDAP_USER_OBJECT_CLASS, \ ENABLE_MULTI_LDAP, MULTI_LDAP_1_SERVER_URL, MULTI_LDAP_1_BASE_DN, MULTI_LDAP_1_ADMIN_DN, \ @@ -1262,7 +1262,20 @@ class AdminUser(APIView): is_active = request.data.get("is_active", None) if is_active: - + keep_sharing = request.data.get("keep_sharing", None) + username = request.user.username + + if keep_sharing and keep_sharing == 'false': + seafile_db = SeafileDB() + orgs = ccnet_api.get_orgs_by_user(email) + if orgs: + org_id = orgs[0].org_id + seafile_db.delete_received_share_by_user(email, org_id) + seafile_db.delete_share_by_user(email, org_id) + else: + seafile_db.delete_received_share_by_user(email) + seafile_db.delete_share_by_user(email) + ExtraSharePermission.objects.filter(share_to=username).delete() try: is_active = to_python_boolean(is_active) except ValueError: diff --git a/seahub/utils/db_api.py b/seahub/utils/db_api.py index d49b0af439..f112430371 100644 --- a/seahub/utils/db_api.py +++ b/seahub/utils/db_api.py @@ -618,3 +618,38 @@ class SeafileDB: wiki_info = WikiInfo(**params) wikis.append(wiki_info) return wikis + + def delete_received_share_by_user(self, username, org_id=''): + # Delete the share content shared to + if org_id: + delete_share_sql = f""" + DELETE FROM `{self.db_name}`.`OrgSharedRepo` WHERE to_email=%s AND org_id=%s + """ + else: + delete_share_sql = f""" + DELETE FROM `{self.db_name}`.`SharedRepo` WHERE to_email=%s + """ + + with connection.cursor() as cursor: + cursor.execute(delete_share_sql, [username, org_id] if org_id else [username]) + + def delete_share_by_user(self, username, org_id=''): + # Delete the share content shared from + if org_id: + delete_share_sql = f""" + DELETE FROM `{self.db_name}`.`OrgSharedRepo` WHERE from_email=%s AND org_id=%s + """ + delete_group_share_sql = f""" + DELETE FROM `{self.db_name}`.`OrgGroupRepo` WHERE owner=%s AND org_id=%s + """ + else: + delete_share_sql = f""" + DELETE FROM `{self.db_name}`.`SharedRepo` WHERE from_email=%s + """ + delete_group_share_sql = f""" + DELETE FROM `{self.db_name}`.`RepoGroup` WHERE user_name=%s + """ + + with connection.cursor() as cursor: + cursor.execute(delete_share_sql, [username, org_id] if org_id else [username]) + cursor.execute(delete_group_share_sql, [username, org_id] if org_id else [username])