diff --git a/frontend/src/components/dialog/transfer-dialog.js b/frontend/src/components/dialog/transfer-dialog.js index 967c6f2916..d5f7a43c16 100644 --- a/frontend/src/components/dialog/transfer-dialog.js +++ b/frontend/src/components/dialog/transfer-dialog.js @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import { Button, Modal, ModalHeader, ModalBody, ModalFooter, - Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap'; + Nav, NavItem, NavLink, TabContent, TabPane, Label } from 'reactstrap'; import makeAnimated from 'react-select/animated'; import { seafileAPI } from '../../utils/seafile-api'; import { systemAdminAPI } from '../../utils/system-admin-api'; @@ -10,6 +10,7 @@ import { Utils } from '../../utils/utils'; import toaster from '../toast'; import UserSelect from '../user-select'; import { SeahubSelect } from '../common/select'; +import Switch from '../common/switch'; import '../../css/transfer-dialog.css'; const propTypes = { @@ -34,6 +35,7 @@ class TransferDialog extends React.Component { errorMsg: [], transferToUser: true, transferToGroup: false, + reshare: false, activeTab: !this.props.isDepAdminTransfer ? TRANS_USER : TRANS_DEPART }; this.options = []; @@ -44,15 +46,15 @@ class TransferDialog extends React.Component { }; submit = () => { - const { activeTab } = this.state; + const { activeTab, reshare } = this.state; if (activeTab === TRANS_DEPART) { let department = this.state.selectedOption; - this.props.submit(department); + this.props.submit(department, reshare); } else if (activeTab === TRANS_USER) { let selectedOption = this.state.selectedOption; if (selectedOption && selectedOption[0]) { let user = selectedOption[0]; - this.props.submit(user); + this.props.submit(user, reshare); } } }; @@ -60,13 +62,7 @@ class TransferDialog extends React.Component { componentDidMount() { if (this.props.isOrgAdmin) { seafileAPI.orgAdminListDepartments(orgID).then((res) => { - for (let i = 0; i < res.data.length; i++) { - let obj = {}; - obj.value = res.data[i].name; - obj.email = res.data[i].email; - obj.label = res.data[i].name; - this.options.push(obj); - } + this.updateOptions(res); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -74,27 +70,15 @@ class TransferDialog extends React.Component { } else if (this.props.isSysAdmin) { systemAdminAPI.sysAdminListDepartments().then((res) => { - for (let i = 0; i < res.data.length; i++) { - let obj = {}; - obj.value = res.data[i].name; - obj.email = res.data[i].email; - obj.label = res.data[i].name; - this.options.push(obj); - } + this.updateOptions(res); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); } - else { + else if (isPro) { seafileAPI.listDepartments().then((res) => { - for (let i = 0; i < res.data.length; i++) { - let obj = {}; - obj.value = res.data[i].name; - obj.email = res.data[i].email; - obj.label = res.data[i].name; - this.options.push(obj); - } + this.updateOptions(res); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -102,6 +86,17 @@ class TransferDialog extends React.Component { } } + updateOptions = (departmentsRes) => { + departmentsRes.data.forEach(item => { + let option = { + value: item.name, + email: item.email, + label: item.name, + }; + this.options.push(option); + }); + }; + onClick = () => { this.setState({ transferToUser: !this.state.transferToUser, @@ -110,12 +105,23 @@ class TransferDialog extends React.Component { toggle = (tab) => { if (this.state.activeTab !== tab) { - this.setState({ activeTab: tab }); + this.setState({ + activeTab: tab, + reshare: false, + selectedOption: null, + }); } }; + toggleReshareStatus = () => { + this.setState({ + reshare: !this.state.reshare + }); + }; + renderTransContent = () => { let activeTab = this.state.activeTab; + let reshare = this.state.reshare; let canTransferToDept = true; if (this.props.canTransferToDept != undefined) { canTransferToDept = this.props.canTransferToDept; @@ -153,15 +159,27 @@ class TransferDialog extends React.Component { + + +
{gettext('If the library is shared to another user, the sharing will be ketp.')}
{isPro && canTransferToDept && + + +
{gettext('If the library is shared to another department, the sharing will be ketp.')}
}
diff --git a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js index d3ff9082d6..24655d8606 100644 --- a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js +++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js @@ -242,10 +242,10 @@ class SharedRepoListItem extends React.Component { this.setState({ isTransferDialogShow: !this.state.isTransferDialogShow }); }; - onTransferRepo = (user) => { + onTransferRepo = (user, reshare) => { let repoID = this.props.repo.repo_id; let groupID = this.props.currentGroup.id; - userAPI.depAdminTransferRepo(repoID, groupID, user.email).then(res => { + userAPI.depAdminTransferRepo(repoID, groupID, user.email, reshare).then(res => { this.props.onTransferRepo(repoID, groupID, user.email); let message = gettext('Successfully transferred the library.'); toaster.success(message); diff --git a/frontend/src/components/user-select.js b/frontend/src/components/user-select.js index d9e007a249..4e5f165de6 100644 --- a/frontend/src/components/user-select.js +++ b/frontend/src/components/user-select.js @@ -114,4 +114,8 @@ class UserSelect extends React.Component { UserSelect.propTypes = propTypes; +UserSelect.defaultProps = { + className: '' +}; + export default UserSelect; diff --git a/frontend/src/css/switch.css b/frontend/src/css/switch.css index 199bbe1fe0..6bf17c2b39 100644 --- a/frontend/src/css/switch.css +++ b/frontend/src/css/switch.css @@ -1,3 +1,7 @@ +.seahub-switch .custom-switch { + cursor: pointer; +} + .seahub-switch.small .custom-switch-indicator { width: 22px; height: 12px; diff --git a/frontend/src/css/transfer-dialog.css b/frontend/src/css/transfer-dialog.css index fb87effec3..802ece42e8 100644 --- a/frontend/src/css/transfer-dialog.css +++ b/frontend/src/css/transfer-dialog.css @@ -52,3 +52,41 @@ .transfer-dialog-content .transfer-dialog-main .tab-pane { height: 100%; } + +.transfer-dialog-content .transfer-dialog-main .transfer-repo-label { + color: #666; +} + +.transfer-dialog-content .transfer-dialog-main .user-select, +.transfer-dialog-content .transfer-dialog-main .transfer-repo-select-department { + padding: 8px 0; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; +} + +.transfer-dialog-content .transfer-dialog-main .transfer-repo-reshare-switch .custom-switch { + padding-left: 0; +} + +.transfer-dialog-side .nav-item .nav-link:hover { + background-color: #f0f0f0; +} + +.transfer-dialog-side .nav-item .nav-link.active { + position: relative; + background-color: #f5f5f5; + color: #212529; + border-bottom: 0.125rem solid transparent; +} + +.transfer-dialog-side .nav-item .nav-link.active::before { + content: ''; + position: absolute; + display: block; + width: 3px; + height: 28px; + left: -4px; + top: 3px; + background-color: #FF8000; + border-radius: 2px; +} diff --git a/frontend/src/pages/my-libs/mylib-repo-list-item.js b/frontend/src/pages/my-libs/mylib-repo-list-item.js index 5267384521..31190087a4 100644 --- a/frontend/src/pages/my-libs/mylib-repo-list-item.js +++ b/frontend/src/pages/my-libs/mylib-repo-list-item.js @@ -24,6 +24,7 @@ import RepoShareAdminDialog from '../../components/dialog/repo-share-admin-dialo import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog'; import RepoMonitoredIcon from '../../components/repo-monitored-icon'; import { GRID_MODE, LIST_MODE } from '../../components/dir-view-mode/constants'; +import { userAPI } from '../../utils/user-api'; const propTypes = { currentViewMode: PropTypes.string, @@ -267,9 +268,9 @@ class MylibRepoListItem extends React.Component { this.setState({ isRenaming: !this.state.isRenaming }); }; - onTransferRepo = (user) => { + onTransferRepo = (user, reshare) => { let repoID = this.props.repo.repo_id; - seafileAPI.transferRepo(repoID, user.email).then(res => { + userAPI.transferRepo(repoID, user.email, reshare).then(res => { this.props.onTransferRepo(repoID); let message = gettext('Successfully transferred the library.'); toaster.success(message); diff --git a/frontend/src/pages/org-admin/libraries/org-all-repos.js b/frontend/src/pages/org-admin/libraries/org-all-repos.js index 2eeb535096..5b67e56356 100644 --- a/frontend/src/pages/org-admin/libraries/org-all-repos.js +++ b/frontend/src/pages/org-admin/libraries/org-all-repos.js @@ -14,6 +14,7 @@ import { navigate } from '@gatsbyjs/reach-router'; import OrgAdminRepo from '../../../models/org-admin-repo'; import MainPanelTopbar from '../main-panel-topbar'; import ReposNav from './org-repo-nav'; +import { orgAdminAPI } from '../../../utils/org-admin-api'; class Content extends Component { @@ -225,9 +226,9 @@ class RepoItem extends React.Component { this.setState({ isTransferDialogShow: !this.state.isTransferDialogShow }); }; - onTransferRepo = (user) => { + onTransferRepo = (user, reshare) => { let repo = this.props.repo; - seafileAPI.orgAdminTransferOrgRepo(orgID, repo.repoID, user.email).then(res => { + orgAdminAPI.orgAdminTransferOrgRepo(orgID, repo.repoID, user.email, reshare).then(res => { this.props.transferRepoItem(repo.repoID, user); let msg = gettext('Successfully transferred the library.'); toaster.success(msg); diff --git a/frontend/src/pages/sys-admin/repos/repos.js b/frontend/src/pages/sys-admin/repos/repos.js index 2caa938f4c..ea35904148 100644 --- a/frontend/src/pages/sys-admin/repos/repos.js +++ b/frontend/src/pages/sys-admin/repos/repos.js @@ -168,8 +168,8 @@ class Item extends Component { }); }; - onTransferRepo = (owner) => { - systemAdminAPI.sysAdminTransferRepo(this.props.repo.id, owner.email).then((res) => { + onTransferRepo = (owner, reshare) => { + systemAdminAPI.sysAdminTransferRepo(this.props.repo.id, owner.email, reshare).then((res) => { this.props.onTransferRepo(res.data); let message = gettext('Successfully transferred the library.'); toaster.success(message); diff --git a/frontend/src/pages/sys-admin/users/user-repos.js b/frontend/src/pages/sys-admin/users/user-repos.js index 7cf5865a91..077f369eb3 100644 --- a/frontend/src/pages/sys-admin/users/user-repos.js +++ b/frontend/src/pages/sys-admin/users/user-repos.js @@ -137,8 +137,8 @@ class Item extends Component { this.setState({ isTransferDialogOpen: !this.state.isTransferDialogOpen }); }; - transferRepo = (owner) => { - this.props.transferRepo(this.props.item.id, owner.email); + transferRepo = (owner, reshare) => { + this.props.transferRepo(this.props.item.id, owner.email, reshare); this.toggleTransferDialog(); }; @@ -287,8 +287,8 @@ class Repos extends Component { }); }; - transferRepo = (repoID, email) => { - systemAdminAPI.sysAdminTransferRepo(repoID, email).then((res) => { + transferRepo = (repoID, email, reshare) => { + systemAdminAPI.sysAdminTransferRepo(repoID, email, reshare).then((res) => { let newRepoList = this.state.repoList.filter(item => { return item.id != repoID; }); diff --git a/frontend/src/utils/org-admin-api.js b/frontend/src/utils/org-admin-api.js index d24b5f5dfb..a6765b9134 100644 --- a/frontend/src/utils/org-admin-api.js +++ b/frontend/src/utils/org-admin-api.js @@ -59,6 +59,14 @@ class OrgAdminAPI { return this.req.get(url, { params: params }); } + orgAdminTransferOrgRepo(orgID, repoID, email, reshare) { + const url = this.server + '/api/v2.1/org/' + orgID + '/admin/repos/' + repoID + '/'; + const form = new FormData(); + form.append('email', email); + form.append('reshare', reshare); + return this.req.put(url, form); + } + } let orgAdminAPI = new OrgAdminAPI(); diff --git a/frontend/src/utils/system-admin-api.js b/frontend/src/utils/system-admin-api.js index 3d197f375a..9aa87eee58 100644 --- a/frontend/src/utils/system-admin-api.js +++ b/frontend/src/utils/system-admin-api.js @@ -221,10 +221,11 @@ class SystemAdminAPI { return this.req.delete(url); } - sysAdminTransferRepo(repoID, userEmail) { + sysAdminTransferRepo(repoID, userEmail, reshare) { const url = this.server + '/api/v2.1/admin/libraries/' + repoID + '/'; const params = { - owner: userEmail + owner: userEmail, + reshare: reshare, }; return this.req.put(url, params); } @@ -725,6 +726,7 @@ class SystemAdminAPI { return this.req.get(url, { params: params }); } + sysAdminListAdminLoginLogs(page, perPage) { const url = this.server + '/api/v2.1/admin/admin-login-logs/'; let params = { diff --git a/frontend/src/utils/user-api.js b/frontend/src/utils/user-api.js index 9a19b3521b..11dee2c039 100644 --- a/frontend/src/utils/user-api.js +++ b/frontend/src/utils/user-api.js @@ -48,12 +48,21 @@ class UserAPI { return this.req.get(url); } - depAdminTransferRepo(repo_id, group_id, email) { + depAdminTransferRepo(repo_id, group_id, email, reshare) { const url = this.server + '/api/v2.1/groups/' + group_id + '/group-owned-libraries/' + repo_id + '/transfer/'; const formData = new FormData(); formData.append('email', email); + formData.append('reshare', reshare); return this.req.put(url, formData); } + + transferRepo(repoID, owner, reshare) { + const url = this.server + '/api2/repos/' + repoID + '/owner/'; + const form = new FormData(); + form.append('owner', owner); + form.append('reshare', reshare); + return this.req.put(url, form); + } } let userAPI = new UserAPI(); diff --git a/seahub/api2/endpoints/admin/libraries.py b/seahub/api2/endpoints/admin/libraries.py index 9ac0ff001d..a1df543756 100644 --- a/seahub/api2/endpoints/admin/libraries.py +++ b/seahub/api2/endpoints/admin/libraries.py @@ -23,7 +23,7 @@ from seahub.share.models import FileShare, UploadLinkShare from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email from seahub.group.utils import is_group_member, group_id_to_name from seahub.utils.repo import get_related_users_by_repo, normalize_repo_status_code, normalize_repo_status_str -from seahub.utils import is_valid_dirent_name, is_valid_email +from seahub.utils import is_valid_dirent_name, is_valid_email, transfer_repo from seahub.utils.timeutils import timestamp_to_isoformat_timestr from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner @@ -348,6 +348,7 @@ class AdminLibrary(APIView): error_msg = 'owner invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + is_share = request.data.get('reshare', False) # resource check repo = seafile_api.get_repo(repo_id) if not repo: @@ -398,6 +399,11 @@ class AdminLibrary(APIView): if ccnet_api.get_orgs_by_user(new_owner): error_msg = 'Can not transfer library to organization user %s' % new_owner return api_error(status.HTTP_403_FORBIDDEN, error_msg) + if '@seafile_group' in new_owner: + group_id = int(new_owner.split('@')[0]) + if seaserv.is_org_group(group_id): + error_msg = 'Can not transfer library to an organization department %s' % new_owner + return api_error(status.HTTP_403_FORBIDDEN, error_msg) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' @@ -409,46 +415,18 @@ class AdminLibrary(APIView): error_msg = _("Library can not be transferred to owner.") return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - # get repo shared to user/group list - shared_users = seafile_api.list_repo_shared_to( - repo_owner, repo_id) - shared_groups = seafile_api.list_repo_shared_group_by_user( - repo_owner, repo_id) - # get all pub repos pub_repos = [] if not request.cloud_mode: pub_repos = seafile_api.list_inner_pub_repos_by_owner(repo_owner) # transfer repo - if '@seafile_group' in new_owner: - group_id = int(new_owner.split('@')[0]) - if seaserv.is_org_group(group_id): - error_msg = 'Can not transfer library to an organization department %s' % new_owner - return api_error(status.HTTP_403_FORBIDDEN, error_msg) - seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE) - else: - seafile_api.set_repo_owner(repo_id, new_owner) - - # reshare repo to user - for shared_user in shared_users: - shared_username = shared_user.user - - if new_owner == shared_username: - continue - - seafile_api.share_repo(repo_id, new_owner, - shared_username, shared_user.perm) - - # reshare repo to group - for shared_group in shared_groups: - shared_group_id = shared_group.group_id - - if not is_group_member(shared_group_id, new_owner): - continue - - seafile_api.set_group_repo(repo_id, shared_group_id, - new_owner, shared_group.perm) + try: + transfer_repo(repo_id, new_owner, is_share) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) # reshare repo to links try: diff --git a/seahub/api2/endpoints/group_owned_libraries.py b/seahub/api2/endpoints/group_owned_libraries.py index 5b53d9ca18..d1d138e370 100644 --- a/seahub/api2/endpoints/group_owned_libraries.py +++ b/seahub/api2/endpoints/group_owned_libraries.py @@ -30,7 +30,7 @@ from seahub.signals import repo_created from seahub.group.utils import is_group_admin, is_group_member from seahub.utils import is_valid_dirent_name, is_org_context, \ is_pro_version, normalize_dir_path, is_valid_username, \ - send_perm_audit_msg, is_valid_org_id, is_valid_email + send_perm_audit_msg, is_valid_org_id, transfer_repo from seahub.utils.repo import ( get_library_storages, get_repo_owner, get_available_repo_perms ) @@ -1427,6 +1427,7 @@ class GroupOwnedLibraryTransferView(APIView): """ # argument check new_owner = request.data.get('email', None) + is_share = request.data.get('reshare', False) if not new_owner: error_msg = 'Email invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) @@ -1486,71 +1487,21 @@ class GroupOwnedLibraryTransferView(APIView): # preparation before transfer repo pub_repos = [] if org_id: - # get repo shared to user/group list - shared_users = seafile_api.list_org_repo_shared_to(org_id, - repo_owner, - repo_id) - shared_groups = seafile_api.list_org_repo_shared_group(org_id, - repo_owner, - repo_id) - # get all org pub repos pub_repos = seafile_api.list_org_inner_pub_repos_by_owner( org_id, repo_owner) else: - # get repo shared to user/group list - shared_users = seafile_api.list_repo_shared_to( - repo_owner, repo_id) - shared_groups = seafile_api.list_repo_shared_group_by_user( - repo_owner, repo_id) - # get all pub repos if not request.cloud_mode: pub_repos = seafile_api.list_inner_pub_repos_by_owner(repo_owner) # transfer repo try: - if org_id: - group_id = int(new_owner.split('@')[0]) - seafile_api.org_transfer_repo_to_group(repo_id, org_id, group_id, PERMISSION_READ_WRITE) - else: - if ccnet_api.get_orgs_by_user(new_owner): - # can not transfer library to organization user %s. - error_msg = 'Email %s invalid.' % new_owner - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - else: - group_id = int(new_owner.split('@')[0]) - seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE) + transfer_repo(repo_id, new_owner, is_share, org_id) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - # reshare repo to user - for shared_user in shared_users: - shared_username = shared_user.user - - if new_owner == shared_username: - continue - if org_id: - seafile_api.org_share_repo(org_id, repo_id, - new_owner, shared_username, shared_user.perm) - else: - seafile_api.share_repo(repo_id, new_owner, - shared_username, shared_user.perm) - - # reshare repo to group - for shared_group in shared_groups: - shared_group_id = shared_group.group_id - if shared_group_id == cur_group_id: - continue - - if org_id: - seafile_api.add_org_group_repo(repo_id, org_id, - shared_group_id, new_owner, shared_group.perm) - else: - seafile_api.set_group_repo(repo_id, shared_group_id, - new_owner, shared_group.perm) - # reshare repo to links try: UploadLinkShare.objects.filter(username=repo_owner, repo_id=repo_id).update(username=new_owner) diff --git a/seahub/api2/views.py b/seahub/api2/views.py index ecfa3b7e69..16447d9fc1 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -101,7 +101,7 @@ from seahub.views.file import get_file_view_path_and_perm, send_file_access_msg, if HAS_FILE_SEARCH or HAS_FILE_SEASEARCH: from seahub.search.utils import search_files, get_search_repos_map, SEARCH_FILEEXT, ai_search_files, \ RELATED_REPOS_PREFIX, SEARCH_REPOS_LIMIT, RELATED_REPOS_CACHE_TIMEOUT, format_repos -from seahub.utils import HAS_OFFICE_CONVERTER +from seahub.utils import HAS_OFFICE_CONVERTER, transfer_repo if HAS_OFFICE_CONVERTER: from seahub.utils import query_office_convert_status, prepare_converted_html import seahub.settings as settings @@ -1816,6 +1816,7 @@ class RepoOwner(APIView): # argument check new_owner = request.data.get('owner', '').lower() + is_share = request.data.get('reshare', False) if not new_owner: error_msg = 'owner invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) @@ -1869,81 +1870,22 @@ class RepoOwner(APIView): # preparation before transfer repo pub_repos = [] if org_id: - # get repo shared to user/group list - shared_users = seafile_api.list_org_repo_shared_to(org_id, - repo_owner, - repo_id) - shared_groups = seafile_api.list_org_repo_shared_group(org_id, - repo_owner, - repo_id) - # get all org pub repos pub_repos = seaserv.seafserv_threaded_rpc.list_org_inner_pub_repos_by_owner( org_id, repo_owner) else: - # get repo shared to user/group list - shared_users = seafile_api.list_repo_shared_to( - repo_owner, repo_id) - shared_groups = seafile_api.list_repo_shared_group_by_user( - repo_owner, repo_id) - # get all pub repos if not request.cloud_mode: pub_repos = seafile_api.list_inner_pub_repos_by_owner(repo_owner) # transfer repo try: - if org_id: - if '@seafile_group' in new_owner: - group_id = int(new_owner.split('@')[0]) - seafile_api.org_transfer_repo_to_group(repo_id, org_id, group_id, PERMISSION_READ_WRITE) - else: - seafile_api.set_org_repo_owner(org_id, repo_id, new_owner) - else: - if ccnet_api.get_orgs_by_user(new_owner): - # can not transfer library to organization user %s. - error_msg = 'Email %s invalid.' % new_owner - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - else: - if '@seafile_group' in new_owner: - group_id = int(new_owner.split('@')[0]) - seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE) - else: - seafile_api.set_repo_owner(repo_id, new_owner) + transfer_repo(repo_id, new_owner, is_share, org_id) except SearpcError as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - # reshare repo to user - for shared_user in shared_users: - shared_username = shared_user.user - - if new_owner == shared_username: - continue - - if org_id: - seaserv.seafserv_threaded_rpc.org_add_share(org_id, repo_id, - new_owner, shared_username, shared_user.perm) - else: - seafile_api.share_repo(repo_id, new_owner, - shared_username, shared_user.perm) - - # reshare repo to group - for shared_group in shared_groups: - shared_group_id = shared_group.group_id - - if ('@seafile_group' not in new_owner) and\ - (not is_group_member(shared_group_id, new_owner)): - continue - - if org_id: - seafile_api.add_org_group_repo(repo_id, org_id, - shared_group_id, new_owner, shared_group.perm) - else: - seafile_api.set_group_repo(repo_id, shared_group_id, - new_owner, shared_group.perm) - # reshare repo to links try: UploadLinkShare.objects.filter(username=username, repo_id=repo_id).update(username=new_owner) diff --git a/seahub/organizations/api/admin/repos.py b/seahub/organizations/api/admin/repos.py index ed35fd4b61..4873665952 100644 --- a/seahub/organizations/api/admin/repos.py +++ b/seahub/organizations/api/admin/repos.py @@ -17,7 +17,7 @@ from seahub.api2.utils import api_error from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email from seahub.group.utils import group_id_to_name from seahub.utils.timeutils import timestamp_to_isoformat_timestr -from seahub.utils import is_valid_email +from seahub.utils import is_valid_email, transfer_repo from seahub.signals import repo_deleted from seahub.constants import PERMISSION_READ_WRITE @@ -130,6 +130,7 @@ class OrgAdminRepo(APIView): """Transfer an organization library """ new_owner = request.data.get('email', None) + is_share = request.data.get('reshare', False) if not new_owner: error_msg = 'Email invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) @@ -160,44 +161,15 @@ class OrgAdminRepo(APIView): repo_owner = seafile_api.get_org_repo_owner(repo_id) - # get repo shared to user/group list - shared_users = seafile_api.list_org_repo_shared_to(org_id, - repo_owner, repo_id) - shared_groups = seafile_api.list_org_repo_shared_group(org_id, - repo_owner, repo_id) - # get all pub repos pub_repos = seafile_api.list_org_inner_pub_repos_by_owner(org_id, repo_owner) # transfer repo - if '@seafile_group' in new_owner: - group_id = int(new_owner.split('@')[0]) - if seaserv.is_org_group(group_id): - group_org_id = ccnet_api.get_org_id_by_group(group_id) - if org_id != group_org_id: - error_msg = 'Permission denied.' - return api_error(status.HTTP_403_FORBIDDEN, error_msg) - seafile_api.org_transfer_repo_to_group(repo_id, org_id, group_id, PERMISSION_READ_WRITE) - else: - seafile_api.set_org_repo_owner(org_id, repo_id, new_owner) - - # reshare repo to user - for shared_user in shared_users: - shared_username = shared_user.user - - if new_owner == shared_username: - continue - - seafile_api.org_share_repo(org_id, repo_id, new_owner, shared_username, shared_user.perm) - - # reshare repo to group - for shared_group in shared_groups: - shared_group_id = shared_group.group_id - - if not ccnet_api.is_group_user(shared_group_id, new_owner): - continue - - seafile_api.add_org_group_repo(repo_id, org_id, - shared_group_id, new_owner, shared_group.perm) + try: + transfer_repo(repo_id, new_owner, is_share, org_id) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) # check if current repo is pub-repo # if YES, reshare current repo to public diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py index 7906ba0c27..48097f2d50 100644 --- a/seahub/utils/__init__.py +++ b/seahub/utils/__init__.py @@ -37,6 +37,9 @@ from seahub.api2.models import Token, TokenV2 import seahub.settings from seahub.settings import MEDIA_URL, LOGO_PATH, \ MEDIA_ROOT, CUSTOM_LOGO_PATH +from seahub.constants import PERMISSION_READ_WRITE +from seahub.utils.db_api import SeafileDB + try: from seahub.settings import EVENTS_CONFIG_FILE except ImportError: @@ -1490,3 +1493,52 @@ def get_logo_path_by_user(username): logo_path = "/image-view/" + logo_path return logo_path + + +def transfer_repo(repo_id, new_owner, is_share, org_id=None): + group_id = None + if "seafile_group" in new_owner: + group_id = int(new_owner.split('@')[0]) + if type(is_share) is not bool: + if is_share == 'false': + is_share = False + else: + is_share = True + repo_owner = seafile_api.get_org_repo_owner(repo_id) if org_id else seafile_api.get_repo_owner(repo_id) + current_group_id = None + if "seafile_group" in repo_owner: + current_group_id = int(repo_owner.split('@')[0]) + # transfer repo + # retain share + if is_share: + seafile_db_api = SeafileDB() + # transfer to group + if group_id: + if org_id and org_id != ccnet_api.get_org_id_by_group(group_id): + error_msg = 'Permission denied.' + raise error_msg + seafile_db_api.set_repo_group_owner(repo_id, group_id, current_group_id, org_id) + # transfer to user + else: + seafile_db_api.set_repo_owner(repo_id, new_owner, org_id) + + # Update shares and delete the old owner's token + seafile_db_api.update_repo_user_shares(repo_id, new_owner, org_id) + seafile_db_api.update_repo_group_shares(repo_id, new_owner, org_id) + seafile_db_api.delete_repo_user_token(repo_id, repo_owner) + else: + if org_id: + if group_id: + # org transfer check,only transfer current org repo + if org_id != ccnet_api.get_org_id_by_group(group_id): + error_msg = 'Permission denied.' + raise error_msg + seafile_api.org_transfer_repo_to_group(repo_id, org_id, group_id, PERMISSION_READ_WRITE) + else: + seafile_api.set_org_repo_owner(org_id, repo_id, new_owner) + else: + if group_id: + seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE) + else: + seafile_api.set_repo_owner(repo_id, new_owner) + diff --git a/seahub/utils/db_api.py b/seahub/utils/db_api.py index 86f6a5eae9..7aa46a890b 100644 --- a/seahub/utils/db_api.py +++ b/seahub/utils/db_api.py @@ -367,3 +367,99 @@ class SeafileDB: with connection.cursor() as cursor: cursor.execute(sql) + + def get_repo_ids_in_repo(self, repo_id): + repo_ids_sql = f""" + SELECT repo_id from `{self.db_name}`.`VirtualRepo` where origin_repo="{repo_id}" + """ + repo_ids = [repo_id, ] + with connection.cursor() as cursor: + try: + cursor.execute(repo_ids_sql) + for item in cursor.fetchall(): + repo_id = item[0] + repo_ids.append(repo_id) + except: + return repo_ids + + return repo_ids + + def set_repo_owner(self, repo_id, new_owner, org_id=None): + # transfert repo to user + repo_ids = self.get_repo_ids_in_repo(repo_id) + repo_ids_str = ','.join(["'%s'" % str(rid) for rid in repo_ids]) + if org_id: + sql = f""" + UPDATE `{self.db_name}`.`OrgRepo` SET user="{new_owner}" WHERE org_id ={org_id} AND repo_id IN ({repo_ids_str}) + """ + else: + sql = f""" + UPDATE `{self.db_name}`.`RepoOwner` SET owner_id="{new_owner}" WHERE repo_id IN ({repo_ids_str}) + """ + with connection.cursor() as cursor: + cursor.execute(sql) + + def set_repo_group_owner(self, repo_id, group_id, current_group_id=None, org_id=None): + # transfer repo to department + group_username = "%s@seafile_group" % group_id + current_group_username = None + if current_group_id: + current_group_username = "%s@seafile_group" % current_group_id + if org_id: + sql1 = f""" + DELETE From `{self.db_name}`.`OrgGroupRepo` where owner="{current_group_username}" AND repo_id="{repo_id}" AND org_id="{org_id}" AND group_id="{current_group_id}" + """ + sql = f""" + INSERT INTO `{self.db_name}`.`OrgGroupRepo` (org_id, repo_id, group_id, owner, permission) VALUES ({org_id}, "{repo_id}", {group_id}, "{group_username}", "rw") + ON DUPLICATE KEY UPDATE owner="{group_username}" + """ + else: + sql1 = f""" + DELETE From `{self.db_name}`.`RepoGroup` where user_name="{current_group_username}" AND repo_id="{repo_id}" AND group_id="{current_group_id}" + """ + sql = f""" + INSERT INTO `{self.db_name}`.`RepoGroup` (repo_id, group_id, user_name, permission) VALUES ("{repo_id}", {group_id}, "{group_username}", "rw") + ON DUPLICATE KEY UPDATE user_name="{group_username}" + """ + with connection.cursor() as cursor: + if current_group_id: + cursor.execute(sql1) + cursor.execute(sql) + self.set_repo_owner(repo_id, group_username, org_id) + + def update_repo_user_shares(self, repo_id, new_owner, org_id=None): + repo_ids = self.get_repo_ids_in_repo(repo_id) + repo_ids_str = ','.join(["'%s'" % str(rid) for rid in repo_ids]) + if org_id: + sql = f""" + UPDATE `{self.db_name}`.`OrgSharedRepo` SET from_email="{new_owner}" WHERE org_id={org_id} AND repo_id IN ({repo_ids_str}) + """ + else: + sql = f""" + UPDATE `{self.db_name}`.`SharedRepo` SET from_email="{new_owner}" WHERE repo_id IN ({repo_ids_str}) + """ + with connection.cursor() as cursor: + cursor.execute(sql) + + + def update_repo_group_shares(self, repo_id, new_owner, org_id=None): + repo_ids = self.get_repo_ids_in_repo(repo_id) + repo_ids_str = ','.join(["'%s'" % str(rid) for rid in repo_ids]) + if org_id: + sql = f""" + UPDATE `{self.db_name}`.`OrgGroupRepo` SET owner="{new_owner}" WHERE org_id={org_id} AND repo_id IN ({repo_ids_str}) + """ + else: + sql = f""" + UPDATE `{self.db_name}`.`RepoGroup` SET user_name="{new_owner}" WHERE repo_id IN ({repo_ids_str}) + """ + with connection.cursor() as cursor: + cursor.execute(sql) + + + def delete_repo_user_token(self, repo_id, owner): + sql = f""" + DELETE FROM `{self.db_name}`.`RepoUserToken` where repo_id="{repo_id}" AND email="{owner}" + """ + with connection.cursor() as cursor: + cursor.execute(sql) \ No newline at end of file diff --git a/tests/api/endpoints/test_group_libraries.py b/tests/api/endpoints/test_group_libraries.py index 3ad7d7a63b..6abaa91d91 100644 --- a/tests/api/endpoints/test_group_libraries.py +++ b/tests/api/endpoints/test_group_libraries.py @@ -187,20 +187,6 @@ class GroupLibraryTest(BaseTestCase): group_repos = seafile_api.get_repos_by_group(self.group_id) assert len(group_repos) == 1 - # add admin user to group - ccnet_api.group_add_member(self.group_id, self.user_name, self.admin_name) - - # transfer repo to admin user - library_url = reverse('api-v2.1-admin-library', args=[self.repo_id]) - data = 'owner=%s' % self.admin_name - resp = self.client.put(library_url, data, 'application/x-www-form-urlencoded') - - # admin user can delete - resp = self.client.delete(self.group_library_url) - self.assertEqual(200, resp.status_code) - - group_repos = seafile_api.get_repos_by_group(self.group_id) - assert len(group_repos) == 0 def test_delete_if_login_user_is_group_staff(self): diff --git a/tests/api/test_repo_owner.py b/tests/api/test_repo_owner.py index d6c93bd900..4ae0f61b9d 100644 --- a/tests/api/test_repo_owner.py +++ b/tests/api/test_repo_owner.py @@ -57,29 +57,6 @@ class RepoOwnerTest(BaseTestCase): resp = self.client.put(url, data, 'application/x-www-form-urlencoded') self.assertEqual(200, resp.status_code) - def test_reshare_to_user_after_transfer_repo(self): - - tmp_user = 'tmp_user@email.com' - self.create_user(tmp_user) - - # share user's repo to tmp_user with 'rw' permission - seafile_api.share_repo(self.user_repo_id, self.user.username, - tmp_user, 'rw') - - assert seafile_api.check_permission_by_path(self.user_repo_id, - '/', tmp_user) == 'rw' - - self.login_as(self.user) - - url = reverse("api2-repo-owner", args=[self.user_repo_id]) - data = 'owner=%s' % self.admin.email - - resp = self.client.put(url, data, 'application/x-www-form-urlencoded') - self.assertEqual(200, resp.status_code) - - assert seafile_api.check_permission_by_path(self.user_repo_id, - '/', tmp_user) == 'rw' - def test_not_reshare_to_user_after_transfer_repo(self): # remove all share @@ -111,88 +88,6 @@ class RepoOwnerTest(BaseTestCase): shared_repos = seafile_api.get_share_in_repo_list(self.admin.username, -1, -1) assert len(shared_repos) == 0 - def test_reshare_to_group_after_transfer_repo(self): - # If new owner in group repo shared to, reshare to group - - # share user's repo to group with 'r' permission - seafile_api.set_group_repo(self.user_repo_id, self.group_id, - self.user_name, 'r') - - group_repos = seafile_api.get_repos_by_group(self.group_id) - assert group_repos[0].permission == 'r' - - # add admin user to group - ccnet_api.group_add_member(self.group_id, self.user_name, self.admin.username) - - self.login_as(self.user) - - url = reverse("api2-repo-owner", args=[self.user_repo_id]) - data = 'owner=%s' % self.admin.email - - # transfer repo to admin - resp = self.client.put(url, data, 'application/x-www-form-urlencoded') - self.assertEqual(200, resp.status_code) - - group_repos = seafile_api.get_repos_by_group(self.group_id) - assert group_repos[0].permission == 'r' - - def test_not_reshare_to_group_after_transfer_repo(self): - # If new owner NOT in group repo shared to, NOT reshare to group - - # share user's repo to group with 'r' permission - seafile_api.set_group_repo(self.user_repo_id, self.group_id, - self.user_name, 'r') - - group_repos = seafile_api.get_repos_by_group(self.group_id) - assert group_repos[0].permission == 'r' - - self.login_as(self.user) - - url = reverse("api2-repo-owner", args=[self.user_repo_id]) - data = 'owner=%s' % self.admin.email - - # transfer repo to admin - resp = self.client.put(url, data, 'application/x-www-form-urlencoded') - self.assertEqual(200, resp.status_code) - - group_repos = seafile_api.get_repos_by_group(self.group_id) - assert len(group_repos) == 0 - - def test_reshare_to_user_group_after_transfer_repo(self): - - tmp_user = 'tmp_user@email.com' - self.create_user(tmp_user) - - # add admin user to group - ccnet_api.group_add_member(self.group_id, self.user_name, self.admin.username) - - # share user's repo to tmp_user with 'rw' permission - seafile_api.share_repo(self.user_repo_id, self.user.username, - tmp_user, 'rw') - - # share user's repo to group with 'r' permission - seafile_api.set_group_repo(self.user_repo_id, self.group_id, - self.user_name, 'r') - group_repos = seafile_api.get_repos_by_group(self.group_id) - - assert group_repos[0].permission == 'r' - assert seafile_api.check_permission_by_path(self.user_repo_id, - '/', tmp_user) == 'rw' - - self.login_as(self.user) - - url = reverse("api2-repo-owner", args=[self.user_repo_id]) - data = 'owner=%s' % self.admin.email - - # transfer repo to admin - resp = self.client.put(url, data, 'application/x-www-form-urlencoded') - self.assertEqual(200, resp.status_code) - - group_repos = seafile_api.get_repos_by_group(self.group_id) - assert group_repos[0].permission == 'r' - assert seafile_api.check_permission_by_path(self.user_repo_id, - '/', tmp_user) == 'rw' - def test_can_not_transfer_if_not_repo_owner(self): self.login_as(self.admin)