diff --git a/LICENSE-thirdparty.txt b/LICENSE-thirdparty.txt index bfe89abfc7..a9c048c797 100644 --- a/LICENSE-thirdparty.txt +++ b/LICENSE-thirdparty.txt @@ -787,31 +787,6 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -====================================================================== -django-post-office (https://github.com/ui/django-post_office) -====================================================================== - -Copyright (c) 2012 Selwin Ong - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ====================================================================== django-statici18n (https://github.com/zyegfryed/django-statici18n) ====================================================================== diff --git a/frontend/src/components/common/account.js b/frontend/src/components/common/account.js index 4df5a81088..9c660233df 100644 --- a/frontend/src/components/common/account.js +++ b/frontend/src/components/common/account.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import { Utils } from '../../utils/utils'; import { seafileAPI } from '../../utils/seafile-api'; -import { siteRoot, gettext, appAvatarURL, logoutUrl } from '../../utils/constants'; +import { siteRoot, gettext, appAvatarURL } from '../../utils/constants'; import toaster from '../toast'; const propTypes = { @@ -164,7 +164,7 @@ class Account extends Component { {gettext('Settings')} {this.renderMenu()} - {gettext('Log out')} + {gettext('Log out')} diff --git a/frontend/src/components/common/logout.js b/frontend/src/components/common/logout.js index bcceb9b63f..863243f6fc 100644 --- a/frontend/src/components/common/logout.js +++ b/frontend/src/components/common/logout.js @@ -1,10 +1,10 @@ import React from 'react'; -import { gettext, logoutUrl } from '../../utils/constants'; +import { siteRoot, gettext } from '../../utils/constants'; export default function Logout() { return ( - + ); -} +} \ No newline at end of file diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index b9c8a43c41..1886127a2e 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -3,7 +3,6 @@ export const gettext = window.gettext; export const siteRoot = window.app.config.siteRoot; export const loginUrl = window.app.config.loginUrl; -export const logoutUrl = window.app.config.logoutUrl; export const avatarInfo = window.app.config.avatarInfo; export const logoPath = window.app.config.logoPath; export const mediaUrl = window.app.config.mediaUrl; diff --git a/seahub/api2/views.py b/seahub/api2/views.py index 21e8e1a048..25f6dcd433 100644 --- a/seahub/api2/views.py +++ b/seahub/api2/views.py @@ -4122,17 +4122,14 @@ class SharedDirView(APIView): return api_error(status.HTTP_403_FORBIDDEN, "Invalid Password") req_path = request.GET.get('p', '/') - - if req_path[-1] != '/': - req_path += '/' + req_path = normalize_dir_path(req_path) if req_path == '/': real_path = fileshare.path else: real_path = posixpath.join(fileshare.path, req_path.lstrip('/')) - if real_path[-1] != '/': # Normalize dir path - real_path += '/' + real_path = normalize_dir_path(real_path) dir_id = seafile_api.get_dir_id_by_path(repo_id, real_path) if not dir_id: diff --git a/seahub/auth/views.py b/seahub/auth/views.py index 5a213429fc..72976231c6 100644 --- a/seahub/auth/views.py +++ b/seahub/auth/views.py @@ -263,6 +263,14 @@ def logout(request, next_page=None, # Local logout for cas user. if getattr(settings, 'ENABLE_CAS', False): response = HttpResponseRedirect(reverse('cas_ng_logout')) + response.delete_cookie('seahub_auth') + return response + + from seahub.settings import LOGOUT_REDIRECT_URL + if LOGOUT_REDIRECT_URL: + response = HttpResponseRedirect(LOGOUT_REDIRECT_URL) + response.delete_cookie('seahub_auth') + return response if redirect_field_name in request.GET: next_page = request.GET[redirect_field_name] diff --git a/seahub/base/context_processors.py b/seahub/base/context_processors.py index ebe0cb0b56..325c974cfd 100644 --- a/seahub/base/context_processors.py +++ b/seahub/base/context_processors.py @@ -136,7 +136,6 @@ def base(request): 'FILE_SERVER_ROOT': file_server_root, 'USE_GO_FILESERVER': seaserv.USE_GO_FILESERVER if hasattr(seaserv, 'USE_GO_FILESERVER') else False, 'LOGIN_URL': dj_settings.LOGIN_URL, - 'LOGOUT_URL': dj_settings.LOGOUT_URL, 'enable_thumbnail': ENABLE_THUMBNAIL, 'thumbnail_size_for_original': THUMBNAIL_SIZE_FOR_ORIGINAL, 'enable_guest_invitation': ENABLE_GUEST_INVITATION, diff --git a/seahub/invitations/templates/invitations/invitation_email.html b/seahub/invitations/templates/invitations/invitation_email.html index ac359b01fe..c3ca802840 100644 --- a/seahub/invitations/templates/invitations/invitation_email.html +++ b/seahub/invitations/templates/invitations/invitation_email.html @@ -1,6 +1,6 @@ {% extends 'email_base.html' %} -{% load i18n %} +{% load i18n seahub_tags%} {% block email_con %} @@ -9,7 +9,7 @@

{% trans "Hi," %}

-{% blocktrans %}{{ inviter }} invited you to join {{ site_name }}. Please click the link below:{% endblocktrans %} +{% blocktrans with inviter_name=inviter|email2nickname %}{{ inviter_name }} invited you to join {{ site_name }}. Please click the link below:{% endblocktrans %}

{{ url_base }}{% url 'invitations:token_view' token %} diff --git a/seahub/onlyoffice/utils.py b/seahub/onlyoffice/utils.py index 451d4f110d..8054c2e719 100644 --- a/seahub/onlyoffice/utils.py +++ b/seahub/onlyoffice/utils.py @@ -33,7 +33,7 @@ def generate_onlyoffice_cache_key(repo_id, file_path): def get_onlyoffice_dict(request, username, repo_id, file_path, file_id='', can_edit=False, can_download=True): - logger.info('{} open file {} in repo {}'.format(username, file_path, repo_id)) + logger.info('{} open file {} in repo {} with can_edit {}'.format(username, file_path, repo_id, can_edit)) repo = seafile_api.get_repo(repo_id) if repo.is_virtual: @@ -55,7 +55,7 @@ def get_onlyoffice_dict(request, username, repo_id, file_path, file_id='', file_id, 'download', username, - use_onetime=True) + use_onetime=False) if not dl_token: return None @@ -67,39 +67,52 @@ def get_onlyoffice_dict(request, username, repo_id, file_path, file_id='', else: document_type = 'text' - cache_key = generate_onlyoffice_cache_key(origin_repo_id, origin_file_path) - doc_key = cache.get(cache_key) - - logger.info('get doc_key {} from cache by cache_key {}'.format(doc_key, cache_key)) - - # temporary solution when failed to get data from cache(django_pylibmc) - # when init process for the first time - if not doc_key: - doc_key = cache.get(cache_key) - - # In theory, file is unlocked when editing finished. - # This can happend if memcache is restarted or memcache is full and doc key is deleted. - if not doc_key and if_locked_by_online_office(repo_id, file_path): - logger.warning('no doc_key in cache and file({} in {}) is locked by online office'.format(file_path, repo_id)) - - if not doc_key: + if not can_edit: info_bytes = force_bytes(origin_repo_id + origin_file_path + file_id) doc_key = hashlib.md5(info_bytes).hexdigest()[:20] - logger.info('generate new doc_key {} by info {}'.format(doc_key, info_bytes)) + else: + cache_key = generate_onlyoffice_cache_key(origin_repo_id, origin_file_path) + doc_key = cache.get(cache_key) - doc_info = json.dumps({'repo_id': origin_repo_id, - 'file_path': origin_file_path, - 'username': username}) - cache.set("ONLYOFFICE_%s" % doc_key, doc_info, None) - logger.info('set doc_key {} and doc_info {} to cache'.format(doc_key, doc_info)) + # temporary solution when failed to get data from cache(django_pylibmc) + # when init process for the first time + if not doc_key: + doc_key = cache.get(cache_key) + if doc_key: + logger.info('get doc_key {} from cache by cache_key {}'.format(doc_key, cache_key)) + else: + # In theory, file is unlocked when editing finished. + # This can happend if memcache is restarted or memcache is full and doc key is deleted. + if if_locked_by_online_office(repo_id, file_path): + logger.warning('no doc_key in cache, but file {} in {} is locked by online office'.format(file_path, repo_id)) + + # generate doc_key + info_bytes = force_bytes(origin_repo_id + origin_file_path + file_id) + doc_key = hashlib.md5(info_bytes).hexdigest()[:20] + logger.info('generate new doc_key {} by repo_id {} file_path {} file_id {}'.format(doc_key, + origin_repo_id, + origin_file_path, + file_id)) + logger.info('set cache_key {} and doc_key {} to cache'.format(cache_key, doc_key)) + cache.set(cache_key, doc_key, None) + + if not cache.get("ONLYOFFICE_%s" % doc_key): + + doc_info = json.dumps({'repo_id': origin_repo_id, + 'file_path': origin_file_path, + 'username': username}) + + cache.set("ONLYOFFICE_%s" % doc_key, doc_info, None) + logger.info('set doc_key {} and doc_info {} to cache'.format(doc_key, doc_info)) + + # for render onlyoffice html file_name = os.path.basename(file_path.rstrip('/')) doc_url = gen_file_get_url(dl_token, file_name) base_url = get_site_scheme_and_netloc() onlyoffice_editor_callback_url = reverse('onlyoffice_editor_callback') - callback_url = urllib.parse.urljoin(base_url, - onlyoffice_editor_callback_url) + callback_url = urllib.parse.urljoin(base_url, onlyoffice_editor_callback_url) return_dict = { 'repo_id': repo_id, diff --git a/seahub/onlyoffice/views.py b/seahub/onlyoffice/views.py index 53e78616e6..14e3ceee7e 100644 --- a/seahub/onlyoffice/views.py +++ b/seahub/onlyoffice/views.py @@ -77,15 +77,19 @@ def onlyoffice_editor_callback(request): post_data = json.loads(request.body) status = int(post_data.get('status', -1)) - if status not in (1, 2, 4, 6): - logger.error('onlyoffice status invalid: {}'.format(status)) + if status == 1: + logger.info('status {}'.format(status)) + return HttpResponse('{"error": 0}') + + if status not in (2, 4, 6): + logger.error('status {}: invalid status'.format(status)) return HttpResponse('{"error": 0}') # get file basic info doc_key = post_data.get('key') doc_info_from_cache = cache.get("ONLYOFFICE_%s" % doc_key) if not doc_info_from_cache: - logger.error('cache.get("ONLYOFFICE_%s" % {}) return None'.format(doc_key)) + logger.error('status {}: can not get doc_info from cache by doc_key {}'.format(status, doc_key)) return HttpResponse('{"error": 0}') doc_info = json.loads(doc_info_from_cache) @@ -136,14 +140,12 @@ def onlyoffice_editor_callback(request): logger.info('status {}: delete cache_key {} from cache'.format(status, cache_key)) cache.delete(cache_key) - if is_pro_version() and if_locked_by_online_office(repo_id, file_path): - seafile_api.unlock_file(repo_id, file_path) + logger.info('status {}: delete doc_key {} from cache'.format(status, doc_key)) + cache.delete("ONLYOFFICE_%s" % doc_key) - # 6 - document is being edited, but the current document state is saved, - if status == 6: - # cache document key when forcesave - logger.info('status {}: set cache_key {} and doc_key {} to cache'.format(status, cache_key, doc_key)) - cache.set(cache_key, doc_key, None) + if is_pro_version() and if_locked_by_online_office(repo_id, file_path): + logger.info('status {}: unlock {} in repo_id {}'.format(status, file_path, repo_id)) + seafile_api.unlock_file(repo_id, file_path) # 4 - document is closed with no changes, if status == 4: @@ -151,7 +153,11 @@ def onlyoffice_editor_callback(request): logger.info('status {}: delete cache_key {} from cache'.format(status, cache_key)) cache.delete(cache_key) + logger.info('status {}: delete doc_key {} from cache'.format(status, doc_key)) + cache.delete("ONLYOFFICE_%s" % doc_key) + if is_pro_version() and if_locked_by_online_office(repo_id, file_path): + logger.info('status {}: unlock {} in repo_id {}'.format(status, file_path, repo_id)) seafile_api.unlock_file(repo_id, file_path) return HttpResponse('{"error": 0}') diff --git a/seahub/settings.py b/seahub/settings.py index 803f602347..72b081ad9e 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -295,7 +295,6 @@ ENABLE_USER_CLEAN_TRASH = True LOGIN_REDIRECT_URL = '/' LOGIN_URL = '/accounts/login/' LOGIN_ERROR_DETAILS = False -LOGOUT_URL = '/accounts/logout/' LOGOUT_REDIRECT_URL = None ACCOUNT_ACTIVATION_DAYS = 7 diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html index ba4db97bca..b099467891 100644 --- a/seahub/templates/base_for_react.html +++ b/seahub/templates/base_for_react.html @@ -40,7 +40,6 @@ siteName: '{{ site_name|escapejs }}', siteRoot: '{{ SITE_ROOT }}', loginUrl: '{{ LOGIN_URL }}', - logoutUrl: '{{ LOGOUT_URL }}', isPro: '{{ is_pro }}', isDocs: '{{ is_docs }}', lang: '{{ LANGUAGE_CODE }}', diff --git a/seahub/two_factor/views/login.py b/seahub/two_factor/views/login.py index b2587b8fd7..f22f78e347 100644 --- a/seahub/two_factor/views/login.py +++ b/seahub/two_factor/views/login.py @@ -179,7 +179,7 @@ class TwoFactorVerifyView(SessionWizardView): device = StaticDevice.objects.device_for_user(self.user.username) context['backup_tokens'] = device.token_set.count() if device else 0 - context['cancel_url'] = settings.LOGOUT_URL + context['cancel_url'] = '/accounts/logout/' context['form_prefix'] = '%s-' % self.steps.current login_bg_image_path = get_login_bg_image_path() context['login_bg_image_path'] = login_bg_image_path diff --git a/seahub/views/file.py b/seahub/views/file.py index aa0f0eb6a9..425c5dc4b2 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -819,9 +819,11 @@ def view_lib_file(request, repo_id, path): if is_pro_version() and can_edit: try: if not is_locked: + logger.info('{} lock {} in repo {} when open it via OnlyOffice.'.format(ONLINE_OFFICE_LOCK_OWNER, path, repo_id)) seafile_api.lock_file(repo_id, path, ONLINE_OFFICE_LOCK_OWNER, int(time.time()) + 40 * 60) elif locked_by_online_office: + logger.info('{} relock {} in repo {} when open it via OnlyOffice.'.format(ONLINE_OFFICE_LOCK_OWNER, path, repo_id)) seafile_api.refresh_file_lock(repo_id, path, int(time.time()) + 40 * 60) except Exception as e: diff --git a/seahub/views/repo.py b/seahub/views/repo.py index d5917ea469..34aa2f7127 100644 --- a/seahub/views/repo.py +++ b/seahub/views/repo.py @@ -24,7 +24,7 @@ from seahub.views import gen_path_link, get_repo_dirents, \ from seahub.utils import gen_dir_share_link, \ gen_shared_upload_link, user_traffic_over_limit, render_error, \ - get_file_type_and_ext, get_service_url + get_file_type_and_ext, get_service_url, normalize_dir_path from seahub.utils.repo import is_repo_owner, get_repo_owner from seahub.settings import ENABLE_UPLOAD_FOLDER, \ ENABLE_RESUMABLE_FILEUPLOAD, ENABLE_THUMBNAIL, \ @@ -57,13 +57,6 @@ def is_password_set(repo_id, username): return seafile_api.is_password_set(repo_id, username) -def get_path_from_request(request): - path = request.GET.get('p', '/') - if path[-1] != '/': - path = path + '/' - return path - - def get_next_url_from_request(request): return request.GET.get('next', None) @@ -121,7 +114,10 @@ def repo_history_view(request, repo_id): raise Http404 username = request.user.username - path = get_path_from_request(request) + + path = request.GET.get('p', '/') + path = normalize_dir_path(path) + user_perm = check_folder_permission(request, repo.id, '/') if user_perm is None: return render_error(request, _('Permission denied')) @@ -277,15 +273,14 @@ def view_shared_dir(request, fileshare): # Get path from frontend, use '/' if missing, and construct request path # with fileshare.path to real path, used to fetch dirents by RPC. req_path = request.GET.get('p', '/') - if req_path[-1] != '/': - req_path += '/' + req_path = normalize_dir_path(req_path) if req_path == '/': real_path = fileshare.path else: real_path = posixpath.join(fileshare.path, req_path.lstrip('/')) - if real_path[-1] != '/': # Normalize dir path - real_path += '/' + + real_path = normalize_dir_path(real_path) repo = get_repo(repo_id) if not repo: