diff --git a/frontend/src/components/main-side-nav.js b/frontend/src/components/main-side-nav.js index e4b4a82a17..f895c9c30b 100644 --- a/frontend/src/components/main-side-nav.js +++ b/frontend/src/components/main-side-nav.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Link } from '@gatsbyjs/reach-router'; import { Badge } from 'reactstrap'; -import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer, enableOCM, enableOCMViaWebdav } from '../utils/constants'; +import { gettext, siteRoot, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer, enableOCM, enableOCMViaWebdav } from '../utils/constants'; import { seafileAPI } from '../utils/seafile-api'; import { Utils } from '../utils/utils'; import toaster from './toast'; @@ -251,14 +251,12 @@ class MainSideNav extends React.Component { } - {canPublishRepo &&
  • this.tabItemClick(e, 'published')}> {gettext('Published Libraries')}
  • - } {isDocs &&
  • this.tabItemClick(e, 'drafts')}> diff --git a/frontend/src/components/share-link-panel/index.js b/frontend/src/components/share-link-panel/index.js index f8f780cb57..35445c8005 100644 --- a/frontend/src/components/share-link-panel/index.js +++ b/frontend/src/components/share-link-panel/index.js @@ -95,15 +95,46 @@ class ShareLinkPanel extends React.Component { }); } - deleteLink = () => { - const { sharedLinkInfo, shareLinks } = this.state; - seafileAPI.deleteShareLink(sharedLinkInfo.token).then(() => { + deleteLink = (token) => { + const { shareLinks } = this.state; + seafileAPI.deleteShareLink(token).then(() => { this.setState({ mode: '', sharedLinkInfo: null, - shareLinks: shareLinks.filter(item => item.token !== sharedLinkInfo.token) + shareLinks: shareLinks.filter(item => item.token !== token) + }); + toaster.success(gettext('Successfully deleted 1 share link')); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + deleteShareLinks = () => { + const { shareLinks } = this.state; + const tokens = shareLinks.filter(item => item.isSelected).map(link => link.token); + seafileAPI.deleteShareLinks(tokens).then(res => { + const { success, failed } = res.data; + if (success.length) { + let newShareLinkList = shareLinks.filter(shareLink => { + return !success.some(deletedShareLink => { + return deletedShareLink.token == shareLink.token; + }); + }); + this.setState({ + shareLinks: newShareLinkList + }); + const length = success.length; + const msg = length == 1 ? + gettext('Successfully deleted 1 share link') : + gettext('Successfully deleted {number_placeholder} share links') + .replace('{number_placeholder}', length); + toaster.success(msg); + } + failed.forEach(item => { + const msg = `${item.token}: ${item.error_msg}`; + toaster.danger(msg); }); - toaster.success(gettext('Link deleted')); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); @@ -209,6 +240,8 @@ class ShareLinkPanel extends React.Component { showLinkDetails={this.showLinkDetails} toggleSelectAllLinks={this.toggleSelectAllLinks} toggleSelectLink={this.toggleSelectLink} + deleteShareLinks={this.deleteShareLinks} + deleteLink={this.deleteLink} /> ); } diff --git a/frontend/src/components/share-link-panel/link-details.js b/frontend/src/components/share-link-panel/link-details.js index 5bd389fb8c..844652fbcc 100644 --- a/frontend/src/components/share-link-panel/link-details.js +++ b/frontend/src/components/share-link-panel/link-details.js @@ -5,6 +5,7 @@ import copy from 'copy-to-clipboard'; import { Button } from 'reactstrap'; import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, canSendShareLinkEmail } from '../../utils/constants'; import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor'; +import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import ShareLink from '../../models/share-link'; @@ -38,7 +39,7 @@ class LinkDetails extends React.Component { expireDays: this.props.defaultExpireDays, expDate: null, isOpIconShown: false, - isNoticeMessageShow: false, + isLinkDeleteDialogOpen: false, isSendLinkShown: false }; } @@ -129,14 +130,20 @@ class LinkDetails extends React.Component { }); } - onNoticeMessageToggle = () => { - this.setState({isNoticeMessageShow: !this.state.isNoticeMessageShow}); + toggleLinkDeleteDialog = () => { + this.setState({isLinkDeleteDialogOpen: !this.state.isLinkDeleteDialogOpen}); } toggleSendLink = () => { this.setState({ isSendLinkShown: !this.state.isSendLinkShown }); } + deleteLink = () => { + const { sharedLinkInfo } = this.props; + const { token } = sharedLinkInfo; + this.props.deleteLink(token); + } + goBack = () => { this.props.showLinkDetails(null); } @@ -235,7 +242,7 @@ class LinkDetails extends React.Component { )} - {(canSendShareLinkEmail && !this.state.isSendLinkShown && !this.state.isNoticeMessageShow) && + {(canSendShareLinkEmail && !this.state.isSendLinkShown) && } {this.state.isSendLinkShown && @@ -246,16 +253,17 @@ class LinkDetails extends React.Component { closeShareDialog={this.props.closeShareDialog} /> } - {(!this.state.isSendLinkShown && !this.state.isNoticeMessageShow) && - + {(!this.state.isSendLinkShown) && + } - {this.state.isNoticeMessageShow && -
    -

    {gettext('Are you sure you want to delete the share link?')}

    -

    {gettext('If the share link is deleted, no one will be able to access it any more.')}

    - {' '} - -
    + {this.state.isLinkDeleteDialogOpen && + } ); diff --git a/frontend/src/components/share-link-panel/link-item.js b/frontend/src/components/share-link-panel/link-item.js index 4d73584301..f7256762d7 100644 --- a/frontend/src/components/share-link-panel/link-item.js +++ b/frontend/src/components/share-link-panel/link-item.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; import copy from 'copy-to-clipboard'; @@ -6,12 +6,14 @@ import toaster from '../toast'; import { isPro, gettext } from '../../utils/constants'; import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor'; import { Utils } from '../../utils/utils'; +import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; const propTypes = { item: PropTypes.object.isRequired, permissionOptions: PropTypes.array, showLinkDetails : PropTypes.func.isRequired, - toggleSelectLink: PropTypes.func.isRequired + toggleSelectLink: PropTypes.func.isRequired, + deleteLink: PropTypes.func.isRequired }; class LinkItem extends React.Component { @@ -19,7 +21,8 @@ class LinkItem extends React.Component { constructor(props) { super(props); this.state = { - isItemOpVisible: false + isItemOpVisible: false, + isDeleteShareLinkDialogOpen: false }; } @@ -40,6 +43,10 @@ class LinkItem extends React.Component { return link.slice(0, 9) + '...' + link.slice(length-5); } + toggleDeleteShareLinkDialog = () => { + this.setState({isDeleteShareLinkDialogOpen: !this.state.isDeleteShareLinkDialogOpen}); + } + copyLink = (e) => { e.preventDefault(); const { item } = this.props; @@ -57,36 +64,55 @@ class LinkItem extends React.Component { this.props.toggleSelectLink(item, e.target.checked); } + deleteLink = () => { + const { item } = this.props; + this.props.deleteLink(item.token); + } + render() { const { isItemOpVisible } = this.state; const { item, permissionOptions } = this.props; const { isSelected = false, permissions, link, expire_date } = item; const currentPermission = Utils.getShareLinkPermissionStr(permissions); return ( - - - - - {this.cutLink(link)} - - {(isPro && permissions) && ( - {}} - /> - )} - - - {expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'} - - - - - - + + + + + + + {this.cutLink(link)} + + + {(isPro && permissions) && ( + {}} + /> + )} + + + {expire_date ? moment(expire_date).format('YYYY-MM-DD HH:mm') : '--'} + + + + + + + + {this.state.isDeleteShareLinkDialogOpen && ( + + )} + ); } } diff --git a/frontend/src/components/share-link-panel/link-list.js b/frontend/src/components/share-link-panel/link-list.js index 2514f09c50..482d5375a7 100644 --- a/frontend/src/components/share-link-panel/link-list.js +++ b/frontend/src/components/share-link-panel/link-list.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { gettext, siteRoot } from '../../utils/constants'; import EmptyTip from '../empty-tip'; import LinkItem from './link-item'; +import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; const propTypes = { shareLinks: PropTypes.array.isRequired, @@ -10,11 +11,24 @@ const propTypes = { setMode: PropTypes.func.isRequired, showLinkDetails: PropTypes.func.isRequired, toggleSelectAllLinks: PropTypes.func.isRequired, - toggleSelectLink: PropTypes.func.isRequired + toggleSelectLink: PropTypes.func.isRequired, + deleteLink: PropTypes.func.isRequired, + deleteShareLinks: PropTypes.func.isRequired }; class LinkList extends React.Component { + constructor(props) { + super(props); + this.state = { + isDeleteShareLinksDialogOpen: false + }; + } + + toggleDeleteShareLinksDialog = () => { + this.setState({isDeleteShareLinksDialogOpen: !this.state.isDeleteShareLinksDialogOpen}); + } + toggleSelectAllLinks = (e) => { this.props.toggleSelectAllLinks(e.target.checked); } @@ -40,7 +54,7 @@ class LinkList extends React.Component {
    {gettext('Share Link')}
    -
    +
    {selectedLinks.length == 0 ? ( <> @@ -49,7 +63,8 @@ class LinkList extends React.Component { ) : ( <> - + + )}
    @@ -67,8 +82,8 @@ class LinkList extends React.Component { {gettext('Link')} {gettext('Permission')} - {gettext('Expiration')} - + {gettext('Expiration')} + @@ -80,12 +95,22 @@ class LinkList extends React.Component { permissionOptions={permissionOptions} showLinkDetails={this.props.showLinkDetails} toggleSelectLink={this.props.toggleSelectLink} + deleteLink={this.props.deleteLink} /> ); })} )} + {this.state.isDeleteShareLinksDialogOpen && ( + + )} ); } diff --git a/frontend/src/css/share-link-dialog.css b/frontend/src/css/share-link-dialog.css index c13ff81ceb..2944237689 100644 --- a/frontend/src/css/share-link-dialog.css +++ b/frontend/src/css/share-link-dialog.css @@ -12,7 +12,7 @@ .share-dialog-content .share-dialog-side { display: flex; - flex: 0 0 22%; + flex-basis: 22%; padding: 1rem; border-bottom: 1px solid #eee; } @@ -32,7 +32,7 @@ .share-dialog-content .share-dialog-main { display: flex; - flex: 0 0 78%; + flex-basis: 78%; padding: 1rem; } diff --git a/frontend/src/pages/wikis/wikis.js b/frontend/src/pages/wikis/wikis.js index 265724ea56..2abd8d9384 100644 --- a/frontend/src/pages/wikis/wikis.js +++ b/frontend/src/pages/wikis/wikis.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Button } from 'reactstrap'; import MediaQuery from 'react-responsive'; import { seafileAPI } from '../../utils/seafile-api'; -import { gettext } from '../../utils/constants'; +import { gettext, canPublishRepo } from '../../utils/constants'; import { Utils } from '../../utils/utils'; import toaster from '../../components/toast'; import ModalPortal from '../../components/modal-portal'; @@ -115,6 +115,7 @@ class Wikis extends Component {
    + {canPublishRepo &&
    @@ -125,6 +126,7 @@ class Wikis extends Component {
    + }
    diff --git a/seahub/api2/endpoints/admin/organizations.py b/seahub/api2/endpoints/admin/organizations.py index b5560493aa..41b4159d9e 100644 --- a/seahub/api2/endpoints/admin/organizations.py +++ b/seahub/api2/endpoints/admin/organizations.py @@ -106,7 +106,7 @@ def gen_org_url_prefix(max_trial=None, length=20): Url prefix if succed, otherwise, ``None``. """ def _gen_prefix(): - url_prefix = 'org_' + get_random_string( + url_prefix = 'org-' + get_random_string( length, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789') if ccnet_api.get_org_by_url_prefix(url_prefix) is not None: logger.error("org url prefix, %s is duplicated" % url_prefix) diff --git a/seahub/api2/endpoints/file.py b/seahub/api2/endpoints/file.py index 0671db97d7..cf34d7c626 100644 --- a/seahub/api2/endpoints/file.py +++ b/seahub/api2/endpoints/file.py @@ -207,10 +207,15 @@ class FileView(APIView): try: seafile_api.post_empty_file(repo_id, parent_dir, new_file_name, username) - except SearpcError as e: - logger.error(e) - error_msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + except Exception as e: + if str(e) == 'Too many files in library.': + error_msg = _("The number of files in library exceeds the limit") + from seahub.api2.views import HTTP_442_TOO_MANY_FILES_IN_LIBRARY + return api_error(HTTP_442_TOO_MANY_FILES_IN_LIBRARY, error_msg) + else: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) if is_draft.lower() == 'true': Draft.objects.add(username, repo, path, file_exist=False) diff --git a/seahub/api2/endpoints/share_link_zip_task.py b/seahub/api2/endpoints/share_link_zip_task.py index ed3e71bb3a..7c167b7ee5 100644 --- a/seahub/api2/endpoints/share_link_zip_task.py +++ b/seahub/api2/endpoints/share_link_zip_task.py @@ -1,6 +1,5 @@ # Copyright (c) 2012-2016 Seafile Ltd. import logging -import stat import os import json import posixpath @@ -10,7 +9,6 @@ from rest_framework.views import APIView from rest_framework import status from django.conf import settings -from django.utils.translation import gettext as _ from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error @@ -22,11 +20,11 @@ from seahub.utils import is_windows_operating_system, is_pro_version, \ from seahub.utils.repo import parse_repo_perm from seahub.settings import ENABLE_SHARE_LINK_AUDIT, SHARE_LINK_LOGIN_REQUIRED -import seaserv from seaserv import seafile_api logger = logging.getLogger(__name__) + class ShareLinkZipTaskView(APIView): throttle_classes = (UserRateThrottle,) @@ -66,6 +64,12 @@ class ShareLinkZipTaskView(APIView): error_msg = 'share_link_token %s not found.' % share_link_token return api_error(status.HTTP_404_NOT_FOUND, error_msg) + # check share link password + if fileshare.is_encrypted() and not check_share_link_access(request, + share_link_token): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + if req_path[-1] != '/': req_path += '/' @@ -94,8 +98,7 @@ class ShareLinkZipTaskView(APIView): return api_error(status.HTTP_403_FORBIDDEN, error_msg) # get file server access token - dir_name = repo.name if real_path == '/' else \ - os.path.basename(real_path.rstrip('/')) + dir_name = repo.name if real_path == '/' else os.path.basename(real_path.rstrip('/')) is_windows = 0 if is_windows_operating_system(request): @@ -155,7 +158,7 @@ class ShareLinkZipTaskView(APIView): # resource check try: - share_link= FileShare.objects.get(token=share_link_token) + share_link = FileShare.objects.get(token=share_link_token) except FileShare.DoesNotExist: error_msg = 'Share link %s not found.' % share_link_token return api_error(status.HTTP_404_NOT_FOUND, error_msg) @@ -171,7 +174,7 @@ class ShareLinkZipTaskView(APIView): return api_error(status.HTTP_404_NOT_FOUND, error_msg) full_parent_dir = posixpath.join(normalize_dir_path(share_link.path), - parent_dir.strip('/')) + parent_dir.strip('/')) full_parent_dir = normalize_dir_path(full_parent_dir) dir_id = seafile_api.get_dir_id_by_path(repo_id, full_parent_dir) if not dir_id: @@ -194,7 +197,7 @@ class ShareLinkZipTaskView(APIView): # check share link password if share_link.is_encrypted() and not check_share_link_access(request, - share_link_token): + share_link_token): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) diff --git a/seahub/api2/endpoints/share_links.py b/seahub/api2/endpoints/share_links.py index 92aaeea9c4..9c3c3f1d43 100644 --- a/seahub/api2/endpoints/share_links.py +++ b/seahub/api2/endpoints/share_links.py @@ -459,6 +459,71 @@ class ShareLinks(APIView): link_info = get_share_link_info(fs) return Response(link_info) + def delete(self, request): + """ Delete share links. + + Permission checking: + 1. default(NOT guest) user; + 2. link owner; + """ + + token_list = request.data.get('tokens') + if not token_list: + error_msg = 'token invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + + username = request.user.username + for token in token_list: + + try: + fs = FileShare.objects.get(token=token) + except FileShare.DoesNotExist: + result['success'].append({ + 'token': token, + }) + continue + + has_published_library = False + if fs.path == '/': + try: + Wiki.objects.get(repo_id=fs.repo_id) + has_published_library = True + except Wiki.DoesNotExist: + pass + + if not fs.is_owner(username): + result['failed'].append({ + 'token': token, + 'error_msg': 'Permission denied.' + }) + continue + + if has_published_library: + result['failed'].append({ + 'token': token, + 'error_msg': _('There is an associated published library.') + }) + continue + + try: + fs.delete() + result['success'].append({ + 'token': token, + }) + except Exception as e: + logger.error(e) + result['failed'].append({ + 'token': token, + 'error_msg': 'Internal Server Error' + }) + continue + + return Response(result) + class ShareLink(APIView): diff --git a/seahub/api2/endpoints/upload_links.py b/seahub/api2/endpoints/upload_links.py index be8f0bb3d9..585efa8a43 100644 --- a/seahub/api2/endpoints/upload_links.py +++ b/seahub/api2/endpoints/upload_links.py @@ -461,19 +461,29 @@ class UploadLinkUpload(APIView): if is_pro_version() and ENABLE_UPLOAD_LINK_VIRUS_CHECK: check_virus = True - if check_virus: - token = seafile_api.get_fileserver_access_token(repo_id, - obj_id, - 'upload-link', - uls.username, - use_onetime=False, - check_virus=check_virus) - else: - token = seafile_api.get_fileserver_access_token(repo_id, - obj_id, - 'upload-link', - uls.username, - use_onetime=False) + try: + if check_virus: + token = seafile_api.get_fileserver_access_token(repo_id, + obj_id, + 'upload-link', + uls.username, + use_onetime=False, + check_virus=check_virus) + else: + token = seafile_api.get_fileserver_access_token(repo_id, + obj_id, + 'upload-link', + uls.username, + use_onetime=False) + except Exception as e: + if str(e) == 'Too many files in library.': + error_msg = _("The number of files in library exceeds the limit") + from seahub.api2.views import HTTP_442_TOO_MANY_FILES_IN_LIBRARY + return api_error(HTTP_442_TOO_MANY_FILES_IN_LIBRARY, error_msg) + else: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) if not token: error_msg = 'Internal Server Error' diff --git a/seahub/api2/endpoints/wikis.py b/seahub/api2/endpoints/wikis.py index 7b39cc45ce..ce174e55b0 100644 --- a/seahub/api2/endpoints/wikis.py +++ b/seahub/api2/endpoints/wikis.py @@ -127,10 +127,15 @@ class WikisView(APIView): if not seafile_api.get_file_id_by_path(repo_id, "/" + page_name): try: seafile_api.post_empty_file(repo_id, '/', page_name, username) - except SearpcError as e: - logger.error(e) - msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, msg) + except Exception as e: + if str(e) == 'Too many files in library.': + error_msg = _("The number of files in library exceeds the limit") + from seahub.api2.views import HTTP_442_TOO_MANY_FILES_IN_LIBRARY + return api_error(HTTP_442_TOO_MANY_FILES_IN_LIBRARY, error_msg) + else: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) fs = FileShare.objects.get_dir_link_by_path(username, repo_id, '/') if not fs: diff --git a/seahub/api2/views.py b/seahub/api2/views.py index bade85992f..4215417b0f 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -147,6 +147,7 @@ json_content_type = 'application/json; charset=utf-8' # Define custom HTTP status code. 4xx starts from 440, 5xx starts from 520. HTTP_440_REPO_PASSWD_REQUIRED = 440 HTTP_441_REPO_PASSWD_MAGIC_REQUIRED = 441 +HTTP_442_TOO_MANY_FILES_IN_LIBRARY = 442 HTTP_443_ABOVE_QUOTA = 443 HTTP_520_OPERATION_FAILED = 520 @@ -1853,12 +1854,22 @@ class UploadLinkView(APIView): return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota.")) obj_id = json.dumps({'parent_dir': parent_dir}) - token = seafile_api.get_fileserver_access_token(repo_id, - obj_id, 'upload', request.user.username, use_onetime=False) + try: + token = seafile_api.get_fileserver_access_token(repo_id, + obj_id, 'upload', request.user.username, use_onetime=False) + except Exception as e: + if str(e) == 'Too many files in library.': + error_msg = _("The number of files in library exceeds the limit") + return api_error(HTTP_442_TOO_MANY_FILES_IN_LIBRARY, error_msg) + else: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) if not token: error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + req_from = request.GET.get('from', 'api') if req_from == 'api': try: diff --git a/seahub/auth/__init__.py b/seahub/auth/__init__.py index a1ea5ca7f0..1e5e4a918e 100644 --- a/seahub/auth/__init__.py +++ b/seahub/auth/__init__.py @@ -73,6 +73,15 @@ def login(request, user): # TODO: It would be nice to support different login methods, like signed cookies. user.last_login = datetime.datetime.now() + # After each ADFS/SAML single sign-on is completed, `_saml2_subject_id` will be recorded in the session, + # so that to distinguish ADFS/SAML users and local users when logging out. + # Therefore, every time login, try to delete `_saml2_subject_id` from the session + # to ensure that `_saml2_subject_id` is brand new and will not interfere with other users' logout. + try: + del request.saml_session['_saml2_subject_id'] + except: + pass + if SESSION_KEY in request.session: if request.session[SESSION_KEY] != user.username: # To avoid reusing another user's session, create a new, empty diff --git a/seahub/auth/views.py b/seahub/auth/views.py index db2d753b36..cc3d19504b 100644 --- a/seahub/auth/views.py +++ b/seahub/auth/views.py @@ -476,14 +476,21 @@ def multi_adfs_sso(request): try: org_saml_config = OrgSAMLConfig.objects.get_config_by_domain(domain) + if not org_saml_config: + render_data['error_msg'] = "Cannot find a SAML/ADFS config for the organization related to domain %s." % domain + return render(request, template_name, render_data) org_id = org_saml_config.org_id org = ccnet_api.get_org_by_id(org_id) + if not org: + render_data['error_msg'] = 'Cannot find an organization related to domain %s.' % domain + return render(request, template_name, render_data) + url_prefix = org.url_prefix except Exception as e: logger.error(e) render_data['error_msg'] = 'Error, please contact administrator.' return render(request, template_name, render_data) - return HttpResponseRedirect('/org/custom/%s/saml2/login/' % org.url_prefix) + return HttpResponseRedirect('/org/custom/%s/saml2/login/' % url_prefix) if request.method == "GET": return render(request, template_name, render_data) diff --git a/seahub/notifications/management/commands/send_notices.py b/seahub/notifications/management/commands/send_notices.py index 41db1ca1b7..7573a573bc 100644 --- a/seahub/notifications/management/commands/send_notices.py +++ b/seahub/notifications/management/commands/send_notices.py @@ -217,6 +217,43 @@ class Command(BaseCommand): notice.avatar_src = self.get_avatar_src(notice.to_user) return notice + def format_repo_monitor_msg(self, notice): + + d = json.loads(notice.detail) + + op_user_email = d['op_user'] + notice.user_url = reverse('user_profile', args=[op_user_email]) + notice.user_name = email2nickname(op_user_email) + notice.avatar_src = self.get_avatar_src(op_user_email) + + notice.op_type = d['op_type'] + + repo_id = d['repo_id'] + repo_name = d['repo_name'] + notice.repo_url = reverse('lib_view', args=[repo_id, repo_name, '']) + notice.repo_name = d['repo_name'] + + obj_type = d['obj_type'] + obj_path_list = d['obj_path_list'] + notice.obj_type = obj_type + notice.obj_path_count = len(obj_path_list) + notice.obj_path_count_minus_one = len(obj_path_list) - 1 + notice.obj_name = os.path.basename(d['obj_path_list'][0]) + + old_obj_path_list = d.get('old_obj_path_list', []) + if old_obj_path_list: + notice.old_obj_name = os.path.basename(d['old_obj_path_list'][0]) + else: + notice.old_obj_name = '' + + if obj_type == 'file': + notice.obj_url = reverse('view_lib_file', args=[repo_id, obj_path_list[0]]) + else: + notice.obj_url = reverse('lib_view', + args=[repo_id, repo_name, obj_path_list[0].strip('/')]) + + return notice + def get_user_language(self, username): return Profile.objects.get_user_language(username) @@ -347,6 +384,9 @@ class Command(BaseCommand): elif notice.is_deleted_files_msg(): notice = self.format_deleted_files_msg(notice) + elif notice.is_repo_monitor_msg(): + notice = self.format_repo_monitor_msg(notice) + if notice is None: continue @@ -354,6 +394,7 @@ class Command(BaseCommand): if not notices: continue + user_name = email2nickname(to_user) contact_email = Profile.objects.get_contact_email_by_user(to_user) c = { @@ -368,8 +409,7 @@ class Command(BaseCommand): 'notifications/notice_email.html', c, None, [contact_email]) # set new last_emailed_time - UserOptions.objects.set_collaborate_last_emailed_time( - to_user, now) + UserOptions.objects.set_collaborate_last_emailed_time(to_user, now) logger.info('Successfully sent email to %s' % contact_email) self.stdout.write('[%s] Successfully sent email to %s' % (str(datetime.datetime.now()), contact_email)) except Exception as e: diff --git a/seahub/notifications/templates/notifications/notice_email.html b/seahub/notifications/templates/notifications/notice_email.html index 10b970ddca..8dd41cc100 100644 --- a/seahub/notifications/templates/notifications/notice_email.html +++ b/seahub/notifications/templates/notifications/notice_email.html @@ -65,6 +65,69 @@ You've got {{num}} new notices on {{ site_name }}: {% elif notice.is_deleted_files_msg %}

    {% blocktrans with repo_url=notice.repo_url repo_name=notice.repo_name %}Your library {{ repo_name }} has recently deleted a large number of files.{% endblocktrans %}

    + {% elif notice.is_repo_monitor_msg %} +

    + {% if notice.obj_type == 'file' %} + {% if notice.op_type == 'create' %} + {% if notice.obj_path_count == 1 %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} created file {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% else %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name obj_path_count_minus_one=notice.obj_path_count_minus_one %} User {{user_name}} created file {{obj_name}} and {{ obj_path_count_minus_one }} other file(s) in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% endif %} + {% if notice.op_type == 'delete' %} + {% if notice.obj_path_count == 1 %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} deleted file {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% else %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name obj_path_count_minus_one=notice.obj_path_count_minus_one %} User {{user_name}} deleted file {{obj_name}} and {{ obj_path_count_minus_one }} other file(s) in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% endif %} + {% if notice.op_type == 'recover' %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} restored file {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% if notice.op_type == 'rename' %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name old_obj_name=notice.old_obj_name %} User {{user_name}} renamed file {{old_obj_name}} to {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% if notice.op_type == 'move' %} + {% if notice.obj_path_count == 1 %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} moved file {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% else %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name obj_path_count_minus_one=notice.obj_path_count_minus_one %} User {{user_name}} moved file {{obj_name}} and {{ obj_path_count_minus_one }} other file(s) in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% endif %} + {% if notice.op_type == 'edit' %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} updated file {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% else %} + {% if notice.op_type == 'create' %} + {% if notice.obj_path_count == 1 %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} created folder {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% else %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name obj_path_count_minus_one=notice.obj_path_count_minus_one %} User {{user_name}} created folder {{obj_name}} and {{ obj_path_count_minus_one }} other folder(s) in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% endif %} + {% if notice.op_type == 'delete' %} + {% if notice.obj_path_count == 1 %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} deleted folder {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% else %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name obj_path_count_minus_one=notice.obj_path_count_minus_one %} User {{user_name}} deleted folder {{obj_name}} and {{ obj_path_count_minus_one }} other folder(s) in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% endif %} + {% if notice.op_type == 'recover' %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} restored folder {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% if notice.op_type == 'rename' %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name old_obj_name=notice.old_obj_name %} User {{user_name}} renamed folder {{old_obj_name}} to {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% if notice.op_type == 'move' %} + {% if notice.obj_path_count == 1 %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name %} User {{user_name}} moved folder {{obj_name}} in library {{repo_name}}.{% endblocktrans %} + {% else %} + {% blocktrans with user_url=notice.user_url user_name=notice.user_name obj_url=notice.obj_url obj_name=notice.obj_name repo_url=notice.repo_url repo_name=notice.repo_name obj_path_count_minus_one=notice.obj_path_count_minus_one %} User {{user_name}} moved folder {{obj_name}} and {{ obj_path_count_minus_one }} other folder(s) in library {{repo_name}}.{% endblocktrans %} + {% endif %} + {% endif %} + {% endif %} +

    {% endif %} {{ notice.timestamp|date:"Y-m-d G:i:s"}} diff --git a/seahub/organizations/views.py b/seahub/organizations/views.py index 3b3d93daa1..b7fbd8943d 100644 --- a/seahub/organizations/views.py +++ b/seahub/organizations/views.py @@ -154,7 +154,7 @@ def gen_org_url_prefix(max_trial=None): Url prefix if succed, otherwise, ``None``. """ def _gen_prefix(): - url_prefix = 'org_' + get_random_string( + url_prefix = 'org-' + get_random_string( 6, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789') if get_org_by_url_prefix(url_prefix) is not None: logger.info("org url prefix, %s is duplicated" % url_prefix)