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