From 82bae67d938d7f0fdf7a15f499cf74d11a55bda3 Mon Sep 17 00:00:00 2001 From: lian Date: Wed, 13 Feb 2019 12:02:55 +0800 Subject: [PATCH] user reset repo password (#2913) * user reset encrypted repo's password * update * send email when user reset encrypted repo's password * resetEncryptedRepoPassword -> resetAndSendEncryptedRepoPassword * update message * update message * bold email when show success msg --- .../reset-encrypted-repo-password-dialog.js | 69 ++++++++++++++++ frontend/src/pages/my-libs/content.js | 19 +++++ frontend/src/pages/my-libs/item.js | 13 ++- frontend/src/pages/my-libs/table-body.js | 2 + frontend/src/utils/constants.js | 2 + .../api2/endpoints/repo_send_new_password.py | 80 +++++++++++++++++++ seahub/api2/endpoints/repo_set_password.py | 2 +- seahub/templates/base_for_react.html | 2 + .../snippets/reset_repo_password.html | 14 ++++ seahub/urls.py | 2 + seahub/views/__init__.py | 7 +- 11 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/dialog/reset-encrypted-repo-password-dialog.js create mode 100644 seahub/api2/endpoints/repo_send_new_password.py create mode 100644 seahub/templates/snippets/reset_repo_password.html diff --git a/frontend/src/components/dialog/reset-encrypted-repo-password-dialog.js b/frontend/src/components/dialog/reset-encrypted-repo-password-dialog.js new file mode 100644 index 0000000000..5728968433 --- /dev/null +++ b/frontend/src/components/dialog/reset-encrypted-repo-password-dialog.js @@ -0,0 +1,69 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import toaster from '../../components/toast'; +import { Button, Form, FormGroup, Label, Input, Modal, ModalHeader, ModalBody, ModalFooter, Alert } from 'reactstrap'; +import { gettext, contactEmail } from '../../utils/constants'; +import { Utils } from '../../utils/utils'; +import { seafileAPI } from '../../utils/seafile-api'; + +const propTypes = { + repoID: PropTypes.string.isRequired, + toggleDialog: PropTypes.func.isRequired, +}; + +class ResetEncryptedRepoPasswordDialog extends React.Component { + + constructor(props) { + super(props); + this.state = { + errMessage: '', + showLoading: true, + showSuccess: false, + showError: false, + }; + } + + componentDidMount() { + seafileAPI.resetAndSendEncryptedRepoPassword(this.props.repoID).then((res) => { + this.setState({showLoading: false}); + this.setState({showSuccess: true}); + }).catch((error) => { + if (error.response) { + this.setState({ + errMessage: error.response.data.error_msg + }); + this.setState({showLoading: false}); + this.setState({showError: true}); + } + }); + } + + render() { + + let user_email = '' + contactEmail + ''; + let message = gettext('New password has been sent to your email {mail}. Please check your mailbox. If you don’t receive the password, please check if your email address is properly configured.').replace('{mail}', user_email); + + return ( + + + {gettext('Reset library password')} + + + {this.state.showLoading && ( + {gettext('Sending new password...')} + )} + {this.state.showSuccess && ( +
+ )} + {this.state.showError && ( + {this.state.errMessage} + )} + + + ); + } +} + +ResetEncryptedRepoPasswordDialog.propTypes = propTypes; + +export default ResetEncryptedRepoPasswordDialog; diff --git a/frontend/src/pages/my-libs/content.js b/frontend/src/pages/my-libs/content.js index c7e2c91dbe..61974752d7 100644 --- a/frontend/src/pages/my-libs/content.js +++ b/frontend/src/pages/my-libs/content.js @@ -6,6 +6,7 @@ import TableBody from './table-body'; import ModalPortal from '../../components/modal-portal'; import LibHistorySetting from '../../components/dialog/lib-history-setting-dialog'; import TransferDialog from '../../components/dialog/transfer-dialog'; +import ResetEncryptedRepoPasswordDialog from '../../components/dialog/reset-encrypted-repo-password-dialog'; import DeleteRepoDialog from '../../components/dialog/delete-repo-dialog'; const propTypes = { @@ -29,6 +30,7 @@ class Content extends Component { this.state = { deleteItemPopupOpen: false, showTransfer: false, + showResetEncryptedRepoPassword: false, itemName: '', showHistorySetting: false, showDetails: false, @@ -67,6 +69,14 @@ class Content extends Component { }); } + onResetEncryptedRepoPassword = (itemName, itemID) => { + this.setState({ + showResetEncryptedRepoPassword: !this.state.showResetEncryptedRepoPassword, + itemName: itemName, + libID: itemID + }); + } + sortByName = (e) => { e.preventDefault(); const sortBy = 'name'; @@ -143,6 +153,7 @@ class Content extends Component { onTransfer={this.onTransfer} showDeleteItemPopup={this.showDeleteItemPopup} onHistorySetting={this.onHistorySetting} + onResetEncryptedRepoPassword={this.onResetEncryptedRepoPassword} /> ); @@ -177,6 +188,14 @@ class Content extends Component { /> } + {this.state.showResetEncryptedRepoPassword && + + + + } ); diff --git a/frontend/src/pages/my-libs/item.js b/frontend/src/pages/my-libs/item.js index 8267675519..6d9b30782f 100644 --- a/frontend/src/pages/my-libs/item.js +++ b/frontend/src/pages/my-libs/item.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import moment from 'moment'; import { Link } from '@reach/router'; import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap'; -import { gettext, siteRoot, storages, folderPermEnabled, enableRepoSnapshotLabel } from '../../utils/constants'; +import { gettext, siteRoot, storages, folderPermEnabled, enableResetEncryptedRepoPassword, isEmailConfigured, enableRepoSnapshotLabel } from '../../utils/constants'; import { Utils } from '../../utils/utils'; import { seafileAPI } from '../../utils/seafile-api'; import Rename from '../../components/rename'; @@ -20,6 +20,7 @@ const propTypes = { onTransfer: PropTypes.func.isRequired, showDeleteItemPopup: PropTypes.func.isRequired, onHistorySetting: PropTypes.func.isRequired, + onResetEncryptedRepoPassword: PropTypes.func.isRequired, onRepoDetails: PropTypes.func.isRequired, onItemClick: PropTypes.func.isRequired }; @@ -150,6 +151,12 @@ class Item extends Component { this.setState({isChangeRepoPasswordDialogOpen: !this.state.isChangeRepoPasswordDialogOpen}); } + resetEncryptedRepoPassword = () => { + const repoName = this.props.data.repo_name; + const repoID = this.props.data.repo_id; + this.props.onResetEncryptedRepoPassword(repoName, repoID); + } + folderPerm = () => { } @@ -171,7 +178,8 @@ class Item extends Component { render() { const data = this.props.data; - + const showResetPasswordMenuItem = data.encrypted && enableResetEncryptedRepoPassword && isEmailConfigured; + data.icon_url = Utils.getLibIconUrl(data); data.icon_title = Utils.getLibIconTitle(data); data.url = `${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`; @@ -200,6 +208,7 @@ class Item extends Component { {gettext('Transfer')} {gettext('History Setting')} {data.encrypted ? {gettext('Change Password')} : ''} + {showResetPasswordMenuItem ? {gettext('Reset Password')} : ''} {folderPermEnabled ? {gettext('Folder Permission')} : ''} {gettext('Details')} diff --git a/frontend/src/pages/my-libs/table-body.js b/frontend/src/pages/my-libs/table-body.js index 82d46d95cb..d11af91dd6 100644 --- a/frontend/src/pages/my-libs/table-body.js +++ b/frontend/src/pages/my-libs/table-body.js @@ -7,6 +7,7 @@ const propTypes = { onRenameRepo: PropTypes.func.isRequired, onDeleteRepo: PropTypes.func.isRequired, onTransfer: PropTypes.func.isRequired, + onResetEncryptedRepoPassword: PropTypes.func.isRequired, showDeleteItemPopup: PropTypes.func.isRequired, onHistorySetting: PropTypes.func.isRequired, onRepoDetails: PropTypes.func.isRequired, @@ -37,6 +38,7 @@ class TableBody extends Component { onRenameRepo={this.props.onRenameRepo} onDeleteRepo={this.props.onDeleteRepo} onTransfer={this.props.onTransfer} + onResetEncryptedRepoPassword={this.props.onResetEncryptedRepoPassword} showDeleteItemPopup={this.props.showDeleteItemPopup} onHistorySetting={this.props.onHistorySetting} onRepoDetails={this.props.onRepoDetails} diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index 3a6c820995..faa1a7eebc 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -27,6 +27,8 @@ export const canViewOrg = window.app.pageOptions.canViewOrg === 'True'; export const fileAuditEnabled = window.app.pageOptions.fileAuditEnabled ? true : false; export const enableFileComment = window.app.pageOptions.enableFileComment ? true : false; export const folderPermEnabled = window.app.pageOptions.folderPermEnabled === 'True'; +export const enableResetEncryptedRepoPassword = window.app.pageOptions.enableResetEncryptedRepoPassword === 'True'; +export const isEmailConfigured = window.app.pageOptions.isEmailConfigured === 'True'; export const enableUploadFolder = window.app.pageOptions.enableUploadFolder === 'True'; export const enableResumableFileUpload = window.app.pageOptions.enableResumableFileUpload === 'True'; export const storages = window.app.pageOptions.storages; // storage backends diff --git a/seahub/api2/endpoints/repo_send_new_password.py b/seahub/api2/endpoints/repo_send_new_password.py new file mode 100644 index 0000000000..48cfadd4ac --- /dev/null +++ b/seahub/api2/endpoints/repo_send_new_password.py @@ -0,0 +1,80 @@ +# Copyright (c) 2012-2016 Seafile Ltd. +import logging + +from rest_framework.authentication import SessionAuthentication +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework import status +from django.utils.translation import ugettext as _ +from django.utils.crypto import get_random_string + +from seaserv import seafile_api + +from seahub.api2.authentication import TokenAuthentication +from seahub.api2.throttling import UserRateThrottle +from seahub.api2.utils import api_error +from seahub.api2.views import HTTP_520_OPERATION_FAILED + +from seahub.utils import IS_EMAIL_CONFIGURED, send_html_email +from seahub.utils.repo import is_repo_owner +from seahub.base.models import RepoSecretKey +from seahub.base.templatetags.seahub_tags import email2contact_email + +from seahub.settings import ENABLE_RESET_ENCRYPTED_REPO_PASSWORD + +logger = logging.getLogger(__name__) + +class RepoSendNewPassword(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle,) + + def post(self, request, repo_id): + """ Only used for reset encrypted repo's password, and then send new + password to user's mainbox. + + Permission checking: + 1. repo owner. + """ + + if not ENABLE_RESET_ENCRYPTED_REPO_PASSWORD or \ + not IS_EMAIL_CONFIGURED: + error_msg = _(u'Feature disabled.') + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + # resource check + repo = seafile_api.get_repo(repo_id) + if not repo: + error_msg = 'Library %s not found.' % repo_id + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + if not repo.encrypted: + error_msg = 'Library %s is not encrypted.' % repo_id + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + # permission check + username = request.user.username + if not is_repo_owner(request, repo_id, username): + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + secret_key = RepoSecretKey.objects.get_secret_key(repo_id) + if not secret_key: + error_msg = _(u"Can not reset this library's password.") + return api_error(HTTP_520_OPERATION_FAILED, error_msg) + + new_password = get_random_string(10) + try: + seafile_api.reset_repo_passwd(repo_id, username, secret_key, new_password) + content = {'repo_name': repo.name, 'password': new_password,} + send_html_email(_(u'New password of library %s') % repo.name, + 'snippets/reset_repo_password.html', content, + None, [email2contact_email(username)]) + except Exception as e: + logger.error(e) + error_msg = 'Internal Server Error' + return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + + return Response({'success': True}) diff --git a/seahub/api2/endpoints/repo_set_password.py b/seahub/api2/endpoints/repo_set_password.py index 8f338541de..2ec35a7fca 100644 --- a/seahub/api2/endpoints/repo_set_password.py +++ b/seahub/api2/endpoints/repo_set_password.py @@ -157,7 +157,7 @@ class RepoSetPassword(APIView): secret_key = RepoSecretKey.objects.get_secret_key(repo_id) if not secret_key: - error_msg = 'repo_id invalid.' + error_msg = _(u"Can not reset this library's password.") return api_error(status.HTTP_400_BAD_REQUEST, error_msg) try: diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html index 79469c17b8..1f62e2720e 100644 --- a/seahub/templates/base_for_react.html +++ b/seahub/templates/base_for_react.html @@ -50,6 +50,8 @@ fileAuditEnabled: '{{ file_audit_enabled }}', enableFileComment: '{{ enable_file_comment }}', folderPermEnabled: '{{ folder_perm_enabled }}', + enableResetEncryptedRepoPassword: '{{ enable_reset_encrypted_repo_password }}', + isEmailConfigured: '{{ is_email_configured }}', enableUploadFolder: '{{ enable_upload_folder }}', enableResumableFileUpload: '{{ enable_resumable_fileupload }}', // storage backends diff --git a/seahub/templates/snippets/reset_repo_password.html b/seahub/templates/snippets/reset_repo_password.html new file mode 100644 index 0000000000..4c2db9e600 --- /dev/null +++ b/seahub/templates/snippets/reset_repo_password.html @@ -0,0 +1,14 @@ +{% extends 'email_base.html' %} + +{% load i18n %} + +{% block email_con %} + +

{% trans "Hi," %}

+ +

+{% blocktrans %}The new password of library {{repo_name}} is {{password}}. Please change it as soon as possible.{% endblocktrans %} + +

+ +{% endblock %} diff --git a/seahub/urls.py b/seahub/urls.py index 955ee5354f..077f5a6d94 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -54,6 +54,7 @@ from seahub.api2.endpoints.repo_trash import RepoTrash from seahub.api2.endpoints.deleted_repos import DeletedRepos from seahub.api2.endpoints.repo_history import RepoHistory from seahub.api2.endpoints.repo_set_password import RepoSetPassword +from seahub.api2.endpoints.repo_send_new_password import RepoSendNewPassword from seahub.api2.endpoints.zip_task import ZipTaskView from seahub.api2.endpoints.share_link_zip_task import ShareLinkZipTaskView from seahub.api2.endpoints.query_zip_progress import QueryZipProgressView @@ -317,6 +318,7 @@ urlpatterns = [ url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/trash/$', RepoTrash.as_view(), name='api-v2.1-repo-trash'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/history/$', RepoHistory.as_view(), name='api-v2.1-repo-history'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/set-password/$', RepoSetPassword.as_view(), name="api-v2.1-repo-set-password"), + url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/send-new-password/$', RepoSendNewPassword.as_view(), name="api-v2.1-repo-send-new-password"), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/repo-tags/$', RepoTagsView.as_view(), name='api-v2.1-repo-tags'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/repo-tags/(?P\d+)/$', RepoTagView.as_view(), name='api-v2.1-repo-tag'), url(r'^api/v2.1/repos/(?P[-0-9a-f]{36})/file-tags/$', RepoFileTagsView.as_view(), name='api-v2.1-file-tags'), diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 00e938ec2f..65c5cdce52 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -43,7 +43,7 @@ from seahub.utils import render_permission_error, render_error, \ get_user_repos, EMPTY_SHA1, gen_file_get_url, \ new_merge_with_no_conflict, get_max_upload_file_size, \ is_pro_version, FILE_AUDIT_ENABLED, is_valid_dirent_name, \ - is_windows_operating_system, get_service_url, seafevents_api + is_windows_operating_system, seafevents_api, IS_EMAIL_CONFIGURED from seahub.utils.star import get_dir_starred_files from seahub.utils.repo import get_library_storages, parse_repo_perm from seahub.utils.file_op import check_file_lock @@ -55,7 +55,8 @@ import seahub.settings as settings from seahub.settings import AVATAR_FILE_STORAGE, \ ENABLE_SUB_LIBRARY, ENABLE_FOLDER_PERM, ENABLE_REPO_SNAPSHOT_LABEL, \ UNREAD_NOTIFICATIONS_REQUEST_INTERVAL, SHARE_LINK_EXPIRE_DAYS_MIN, \ - SHARE_LINK_EXPIRE_DAYS_MAX, SHARE_LINK_EXPIRE_DAYS_DEFAULT, SEAFILE_COLLAB_SERVER + SHARE_LINK_EXPIRE_DAYS_MAX, SHARE_LINK_EXPIRE_DAYS_DEFAULT, \ + SEAFILE_COLLAB_SERVER, ENABLE_RESET_ENCRYPTED_REPO_PASSWORD from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP from seahub.onlyoffice.settings import ENABLE_ONLYOFFICE @@ -1225,6 +1226,8 @@ def react_fake_view(request, **kwargs): 'share_link_expire_days_max': SHARE_LINK_EXPIRE_DAYS_MAX, 'enable_encrypted_library': config.ENABLE_ENCRYPTED_LIBRARY, 'enable_repo_history_setting': config.ENABLE_REPO_HISTORY_SETTING, + 'enable_reset_encrypted_repo_password': ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, + 'is_email_configured': IS_EMAIL_CONFIGURED, }) @login_required