From 8a6d5e163915c175be7b0cf8369aa5ef0ad99d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=A1=BA=E5=BC=BA?= Date: Tue, 20 Oct 2020 11:31:40 +0800 Subject: [PATCH 01/18] repair no userPerm bug (#4696) --- frontend/src/pages/lib-content-view/lib-content-toolbar.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/pages/lib-content-view/lib-content-toolbar.js b/frontend/src/pages/lib-content-view/lib-content-toolbar.js index 7f868f45a2..ee5fcd51df 100644 --- a/frontend/src/pages/lib-content-view/lib-content-toolbar.js +++ b/frontend/src/pages/lib-content-view/lib-content-toolbar.js @@ -55,10 +55,6 @@ class LibContentToolbar extends React.Component { render() { - if (!this.props.userPerm) { - return
- } - if (this.props.isViewFile) { return ( From c331f020899b7abf74c1d865425edd6d0cc1c8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=A1=BA=E5=BC=BA?= Date: Tue, 20 Oct 2020 12:21:11 +0800 Subject: [PATCH 02/18] Repair md save bug (#4698) * repair md save bug * optimize code --- frontend/src/markdown-editor.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/markdown-editor.js b/frontend/src/markdown-editor.js index 72bced3cde..7972928f0d 100644 --- a/frontend/src/markdown-editor.js +++ b/frontend/src/markdown-editor.js @@ -25,7 +25,7 @@ const { repoID, repoName, filePath, fileName, mode, draftID, isDraft, hasDraft, const { siteRoot, serviceUrl, seafileCollabServer } = window.app.config; const userInfo = window.app.userInfo; const userName = userInfo.username; -let dirPath = '/'; +let dirPath = Utils.getDirName(filePath); function getImageFileNameWithTimestamp() { var d = Date.now(); @@ -143,7 +143,8 @@ class EditorUtilities { } getFiles() { - return seafileAPI.listDir(repoID, dirPath, { recursive: true} ).then((response) => { + const rootPath = '/'; + return seafileAPI.listDir(repoID, rootPath, { recursive: true} ).then((response) => { var files = response.data.dirent_list.map((item) => { return { name: item.name, From 711f4f72fba99432eb65b40707581791e226bb42 Mon Sep 17 00:00:00 2001 From: lian Date: Tue, 20 Oct 2020 21:32:50 +0800 Subject: [PATCH 03/18] update work weixin login (#4700) Co-authored-by: lian --- seahub/work_weixin/settings.py | 4 ++++ seahub/work_weixin/views.py | 14 +++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/seahub/work_weixin/settings.py b/seahub/work_weixin/settings.py index 239909746f..a780b9ccc4 100644 --- a/seahub/work_weixin/settings.py +++ b/seahub/work_weixin/settings.py @@ -26,6 +26,10 @@ WORK_WEIXIN_GET_USER_INFO_URL = getattr(settings, 'WORK_WEIXIN_GET_USER_INFO_URL WORK_WEIXIN_GET_USER_PROFILE_URL = getattr(settings, 'WORK_WEIXIN_GET_USER_PROFILE_URL', 'https://qyapi.weixin.qq.com/cgi-bin/user/get') + +MP_WORK_WEIXIN_AUTHORIZATION_URL = getattr(settings, 'MP_WORK_WEIXIN_AUTHORIZATION_URL', + 'https://open.weixin.qq.com/connect/oauth2/authorize') + # # work weixin notifications WORK_WEIXIN_NOTIFICATIONS_URL = getattr(settings, 'WORK_WEIXIN_NOTIFICATIONS_URL', 'https://qyapi.weixin.qq.com/cgi-bin/message/send') diff --git a/seahub/work_weixin/views.py b/seahub/work_weixin/views.py index ffbaf13cdd..c0622bb7a7 100644 --- a/seahub/work_weixin/views.py +++ b/seahub/work_weixin/views.py @@ -4,7 +4,9 @@ import uuid import logging import requests -import urllib.request, urllib.parse, urllib.error +import urllib.request +import urllib.parse +import urllib.error from django.http import HttpResponseRedirect from django.utils.translation import ugettext as _ @@ -15,7 +17,7 @@ from seahub import auth from seahub.utils import render_error from seahub.base.accounts import User from seahub.work_weixin.settings import WORK_WEIXIN_AUTHORIZATION_URL, WORK_WEIXIN_CORP_ID, \ - WORK_WEIXIN_AGENT_ID, WORK_WEIXIN_PROVIDER, \ + WORK_WEIXIN_AGENT_ID, WORK_WEIXIN_PROVIDER, MP_WORK_WEIXIN_AUTHORIZATION_URL, \ WORK_WEIXIN_GET_USER_INFO_URL, WORK_WEIXIN_GET_USER_PROFILE_URL, WORK_WEIXIN_UID_PREFIX, \ WORK_WEIXIN_USER_INFO_AUTO_UPDATE, REMEMBER_ME from seahub.work_weixin.utils import work_weixin_oauth_check, get_work_weixin_access_token, \ @@ -44,7 +46,13 @@ def work_weixin_oauth_login(request): 'redirect_uri': get_site_scheme_and_netloc() + reverse('work_weixin_oauth_callback'), 'state': state, } - authorization_url = WORK_WEIXIN_AUTHORIZATION_URL + '?' + urllib.parse.urlencode(data) + + if 'micromessenger' in request.META.get('HTTP_USER_AGENT').lower(): + data['response_type'] = 'code' + data['scope'] = 'snsapi_base' + authorization_url = MP_WORK_WEIXIN_AUTHORIZATION_URL + '?' + urllib.parse.urlencode(data) + '#wechat_redirect' + else: + authorization_url = WORK_WEIXIN_AUTHORIZATION_URL + '?' + urllib.parse.urlencode(data) return HttpResponseRedirect(authorization_url) From af691dab1bad9d0e45915e7440587c09f305ef17 Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 23 Oct 2020 16:18:03 +0800 Subject: [PATCH 04/18] get auth token on profile page (#4703) * get auth token on profile page * [user settings] web api auth token: fixup & improvement Co-authored-by: lian Co-authored-by: llj --- .../user-settings/web-api-auth-token.js | 41 +++++++++++++++++++ frontend/src/settings.js | 9 +++- .../api2/endpoints/auth_token_by_session.py | 9 +++- .../templates/profile/set_profile_react.html | 4 +- seahub/profile/views.py | 1 + seahub/settings.py | 3 ++ 6 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components/user-settings/web-api-auth-token.js diff --git a/frontend/src/components/user-settings/web-api-auth-token.js b/frontend/src/components/user-settings/web-api-auth-token.js new file mode 100644 index 0000000000..54533aee01 --- /dev/null +++ b/frontend/src/components/user-settings/web-api-auth-token.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { gettext } from '../../utils/constants'; +import { seafileAPI } from '../../utils/seafile-api'; +import { Utils } from '../../utils/utils'; +import toaster from '../toast'; + +class WebAPIAuthToken extends React.Component { + + constructor(props) { + super(props); + this.state = { + authToken: '******' + }; + } + + getAuthToken = () => { + seafileAPI.getAuthTokenBySession().then((res) => { + this.setState({ + authToken: res.data.token + }); + }).catch((error) => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + render() { + const { authToken } = this.state; + return ( +
+

{gettext('Web API Auth Token')}

+
+ + +
+
+ ); + } +} + +export default WebAPIAuthToken; diff --git a/frontend/src/settings.js b/frontend/src/settings.js index 0215241ee9..11f52afa69 100644 --- a/frontend/src/settings.js +++ b/frontend/src/settings.js @@ -9,6 +9,7 @@ import CommonToolbar from './components/toolbar/common-toolbar'; import SideNav from './components/user-settings/side-nav'; import UserAvatarForm from './components/user-settings/user-avatar-form'; import UserBasicInfoForm from './components/user-settings/user-basic-info-form'; +import WebAPIAuthToken from './components/user-settings/web-api-auth-token'; import WebdavPassword from './components/user-settings/webdav-password'; import LanguageSetting from './components/user-settings/language-setting'; import ListInAddressBook from './components/user-settings/list-in-address-book'; @@ -25,8 +26,9 @@ import './css/user-settings.css'; const { canUpdatePassword, passwordOperationText, - enableAddressBook, + enableGetAuthToken, enableWebdavSecret, + enableAddressBook, twoFactorAuthEnabled, enableWechatWork, enableDingtalk, @@ -40,6 +42,7 @@ class Settings extends React.Component { this.sideNavItems = [ {show: true, href: '#user-basic-info', text: gettext('Profile')}, {show: canUpdatePassword, href: '#update-user-passwd', text: gettext('Password')}, + {show: enableGetAuthToken, href: '#get-auth-token', text: gettext('Web API Auth Token')}, {show: enableWebdavSecret, href: '#update-webdav-passwd', text: gettext('WebDav Password')}, {show: enableAddressBook, href: '#list-in-address-book', text: gettext('Global Address Book')}, {show: true, href: '#lang-setting', text: gettext('Language')}, @@ -129,9 +132,11 @@ class Settings extends React.Component { {passwordOperationText} } + + {enableGetAuthToken && } {enableWebdavSecret && } {enableAddressBook && this.state.userInfo && - } + } {isPro && } {twoFactorAuthEnabled && } diff --git a/seahub/api2/endpoints/auth_token_by_session.py b/seahub/api2/endpoints/auth_token_by_session.py index bd58b32c57..b3e4039b9c 100644 --- a/seahub/api2/endpoints/auth_token_by_session.py +++ b/seahub/api2/endpoints/auth_token_by_session.py @@ -3,9 +3,12 @@ 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 seahub.api2.throttling import UserRateThrottle -from seahub.api2.utils import get_token_v1 +from seahub.api2.utils import get_token_v1, api_error + +from seahub.settings import ENABLE_GET_AUTH_TOKEN_BY_SESSION class AuthTokenBySession(APIView): @@ -18,6 +21,10 @@ class AuthTokenBySession(APIView): def get(self, request): + if not ENABLE_GET_AUTH_TOKEN_BY_SESSION: + error_msg = 'Feature is not enabled.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + token = get_token_v1(request.user.username) return Response({'token': token.key}) diff --git a/seahub/profile/templates/profile/set_profile_react.html b/seahub/profile/templates/profile/set_profile_react.html index 52c2a504ed..0f576553b6 100644 --- a/seahub/profile/templates/profile/set_profile_react.html +++ b/seahub/profile/templates/profile/set_profile_react.html @@ -18,12 +18,14 @@ window.app.pageOptions = { enableUpdateUserInfo: {% if ENABLE_UPDATE_USER_INFO %} true {% else %} false {% endif %}, nameLabel: "{% trans "Name:" context "true name" %}", enableUserSetContactEmail: {% if ENABLE_USER_SET_CONTACT_EMAIL %} true {% else %} false {% endif %}, - + canUpdatePassword: {% if not is_ldap_user and ENABLE_CHANGE_PASSWORD %} true {% else %} false {% endif %}, {% if not is_ldap_user and ENABLE_CHANGE_PASSWORD %} passwordOperationText: {% if user_unusable_password %}"{% trans "Set Password" %}"{% else %}"{% trans "Update" %}"{% endif %}, {% endif %} + enableGetAuthToken: {% if ENABLE_GET_AUTH_TOKEN_BY_SESSION %} true {% else %} false {% endif %}, + enableWebdavSecret: {% if ENABLE_WEBDAV_SECRET %} true {% else %} false {% endif %}, {% if ENABLE_WEBDAV_SECRET %} webdavPasswd: '{{ webdav_passwd|escapejs }}', diff --git a/seahub/profile/views.py b/seahub/profile/views.py index 56c03f7811..47132865fa 100644 --- a/seahub/profile/views.py +++ b/seahub/profile/views.py @@ -118,6 +118,7 @@ def edit_profile(request): 'is_ldap_user': is_ldap_user(request.user), 'two_factor_auth_enabled': has_two_factor_auth(), 'ENABLE_CHANGE_PASSWORD': settings.ENABLE_CHANGE_PASSWORD, + 'ENABLE_GET_AUTH_TOKEN_BY_SESSION': settings.ENABLE_GET_AUTH_TOKEN_BY_SESSION, 'ENABLE_WEBDAV_SECRET': settings.ENABLE_WEBDAV_SECRET, 'ENABLE_DELETE_ACCOUNT': ENABLE_DELETE_ACCOUNT, 'ENABLE_UPDATE_USER_INFO': ENABLE_UPDATE_USER_INFO, diff --git a/seahub/settings.py b/seahub/settings.py index f9a4c910a8..49cd7da6c4 100644 --- a/seahub/settings.py +++ b/seahub/settings.py @@ -392,6 +392,9 @@ FORCE_PASSWORD_CHANGE = True # Enable a user to change password in 'settings' page. ENABLE_CHANGE_PASSWORD = True +# Enable a user to get auth token in 'settings' page. +ENABLE_GET_AUTH_TOKEN_BY_SESSION = False + ENABLE_DELETE_ACCOUNT = True ENABLE_UPDATE_USER_INFO = True From e49ff53762366246521bf345b5b7762128271571 Mon Sep 17 00:00:00 2001 From: llj Date: Fri, 23 Oct 2020 17:02:23 +0800 Subject: [PATCH 05/18] [seafile-js] updated the version --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index c4b4bf626f..82288860b7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,7 +44,7 @@ "react-responsive": "^6.1.2", "react-select": "^2.4.1", "reactstrap": "^6.4.0", - "seafile-js": "0.2.156", + "seafile-js": "0.2.158", "socket.io-client": "^2.2.0", "sw-precache-webpack-plugin": "0.11.4", "unified": "^7.0.0", From 2bb4074e71a570b31cb27cf6a799384b57a96e32 Mon Sep 17 00:00:00 2001 From: lian Date: Mon, 26 Oct 2020 16:43:45 +0800 Subject: [PATCH 06/18] group member pagination (#4704) * group member pagination * update * update * update Co-authored-by: lian --- .../departments/department-detail.js | 68 ++++++++++++++++--- .../pages/sys-admin/groups/group-members.js | 62 +++++++++++++++-- .../endpoints/admin/address_book/groups.py | 13 ---- seahub/api2/endpoints/admin/group_members.py | 28 ++++++-- seahub/api2/endpoints/group_members.py | 12 +++- 5 files changed, 147 insertions(+), 36 deletions(-) diff --git a/frontend/src/pages/sys-admin/departments/department-detail.js b/frontend/src/pages/sys-admin/departments/department-detail.js index 4d4dddccf4..87d1fe9f5d 100644 --- a/frontend/src/pages/sys-admin/departments/department-detail.js +++ b/frontend/src/pages/sys-admin/departments/department-detail.js @@ -2,6 +2,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; import { Link } from '@reach/router'; +import Paginator from '../../../components/paginator'; import { seafileAPI } from '../../../utils/seafile-api'; import { Utils } from '../../../utils/utils.js'; import toaster from '../../../components/toast'; @@ -35,6 +36,11 @@ class DepartmentDetail extends React.Component { isItemFreezed: false, ancestorGroups: [], members: [], + membersErrorMsg: '', + membersPageInfo: { + }, + membersPage: 1, + membersPerPage: 25, deletedMember: {}, isShowAddMemberDialog: false, showDeleteMemberDialog: false, @@ -54,13 +60,15 @@ class DepartmentDetail extends React.Component { componentDidMount() { const groupID = this.props.groupID; this.listGroupRepo(groupID); - this.listMembers(groupID); + this.getDepartmentInfo(groupID); + this.listMembers(groupID, this.state.membersPage, this.state.membersPerPage); } componentWillReceiveProps(nextProps) { if (this.props.groupID !== nextProps.groupID) { this.listGroupRepo(nextProps.groupID); - this.listMembers(nextProps.groupID); + this.getDepartmentInfo(nextProps.groupID); + this.listMembers(nextProps.groupID, this.state.membersPage, this.state.membersPerPage); } } @@ -73,10 +81,9 @@ class DepartmentDetail extends React.Component { }); } - listMembers = (groupID) => { + getDepartmentInfo = (groupID) => { seafileAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => { this.setState({ - members: res.data.members, groups: res.data.groups, ancestorGroups: res.data.ancestor_groups, groupName: res.data.name, @@ -87,6 +94,34 @@ class DepartmentDetail extends React.Component { }); } + listMembers = (groupID, page, perPage) => { + seafileAPI.sysAdminListGroupMembers(groupID, page, perPage).then((res) => { + this.setState({ + members: res.data.members, + membersPageInfo: res.data.page_info + }); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + this.setState({membersErrorMsg: errMessage}); + }); + } + + getPreviousPageList = () => { + this.listMembers(this.props.groupID, this.state.membersPageInfo.current_page - 1, this.state.membersPerPage); + } + + getNextPageList = () => { + this.listMembers(this.props.groupID, this.state.membersPageInfo.current_page + 1, this.state.membersPerPage); + } + + resetPerPage = (perPage) => { + this.setState({ + membersPerPage: perPage + }, () => { + this.listMembers(this.props.groupID, 1, perPage); + }); + } + listSubDepartGroups = (groupID) => { seafileAPI.sysAdminGetDepartmentInfo(groupID, true).then(res => { this.setState({ groups: res.data.groups }); @@ -114,7 +149,7 @@ class DepartmentDetail extends React.Component { } onMemberChanged = () => { - this.listMembers(this.props.groupID); + this.listMembers(this.props.groupID, this.state.membersPageInfo.current_page, this.state.membersPerPage); } toggleItemFreezed = (isFreezed) => { @@ -142,7 +177,7 @@ class DepartmentDetail extends React.Component { } showDeleteDepartDialog = (subGroup) => { - this.setState({ + this.setState({ showDeleteDepartDialog: true, subGroupID: subGroup.id, subGroupName: subGroup.name @@ -157,7 +192,7 @@ class DepartmentDetail extends React.Component { } render() { - const { members, repos, groups } = this.state; + const { members, membersErrorMsg, repos, groups } = this.state; const groupID = this.props.groupID; const topBtn = 'btn btn-secondary operation-item'; const topbarChildren = ( @@ -207,7 +242,7 @@ class DepartmentDetail extends React.Component {

- {groupID ? + {groupID ? {gettext('Departments')} : {gettext('Departments')} } @@ -253,14 +288,16 @@ class DepartmentDetail extends React.Component { }

- +

{gettext('Members')}

- {(members && members.length === 1 && members[0].role === 'Owner') ? + {membersErrorMsg ?

{membersErrorMsg}

: + members.length == 0 ?

{gettext('No members')}

: + @@ -287,6 +324,17 @@ class DepartmentDetail extends React.Component { })}
+ {this.state.membersPageInfo && + + } +
}
diff --git a/frontend/src/pages/sys-admin/groups/group-members.js b/frontend/src/pages/sys-admin/groups/group-members.js index a82c2e43aa..88eecb8d1c 100644 --- a/frontend/src/pages/sys-admin/groups/group-members.js +++ b/frontend/src/pages/sys-admin/groups/group-members.js @@ -6,6 +6,7 @@ import { siteRoot, gettext } from '../../../utils/constants'; import toaster from '../../../components/toast'; import EmptyTip from '../../../components/empty-tip'; import Loading from '../../../components/loading'; +import Paginator from '../../../components/paginator'; import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog'; import SysAdminGroupAddMemberDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-group-add-member-dialog'; import SysAdminGroupRoleEditor from '../../../components/select-editor/sysadmin-group-role-editor'; @@ -19,8 +20,16 @@ class Content extends Component { super(props); } + getPreviousPageList = () => { + this.props.getListByPage(this.props.pageInfo.current_page - 1); + } + + getNextPageList = () => { + this.props.getListByPage(this.props.pageInfo.current_page + 1); + } + render() { - const { loading, errorMsg, items } = this.props; + const { loading, errorMsg, items, pageInfo, curPerPage } = this.props; if (loading) { return ; } else if (errorMsg) { @@ -53,9 +62,19 @@ class Content extends Component { })} + {pageInfo && + + }
); - return items.length ? table : emptyTip; + return items.length ? table : emptyTip; } } } @@ -110,7 +129,7 @@ class Item extends Component { {item.role == 'Owner' ? gettext('Owner') : - {isDeleteDialogOpen && - { + + let urlParams = (new URL(window.location)).searchParams; + const { currentPage, perPage } = this.state; + this.setState({ + currentPage: parseInt(urlParams.get('page') || currentPage), + perPage: parseInt(urlParams.get('per_page') || perPage) + }, () => { + this.getListByPage(this.state.currentPage); + }); + } + + getListByPage = (page) => { + const { perPage } = this.state; + seafileAPI.sysAdminListGroupMembers(this.props.groupID, page, perPage).then((res) => { this.setState({ loading: false, memberList: res.data.members, - groupName: res.data.group_name + groupName: res.data.group_name, + pageInfo: res.data.page_info }); }).catch((error) => { this.setState({ @@ -167,6 +203,14 @@ class GroupMembers extends Component { }); } + resetPerPage = (perPage) => { + this.setState({ + perPage: perPage + }, () => { + this.getListByPage(1); + }); + } + toggleAddMemgerDialog = () => { this.setState({isAddMemberDialogOpen: !this.state.isAddMemberDialogOpen}); } @@ -238,7 +282,7 @@ class GroupMembers extends Component {
-
diff --git a/seahub/api2/endpoints/admin/address_book/groups.py b/seahub/api2/endpoints/admin/address_book/groups.py index 4b2083f9d2..f678f91dae 100644 --- a/seahub/api2/endpoints/admin/address_book/groups.py +++ b/seahub/api2/endpoints/admin/address_book/groups.py @@ -184,20 +184,7 @@ class AdminAddressBookGroup(APIView): for group in groups: ret_groups.append(address_book_group_to_dict(group)) - try: - members = ccnet_api.get_group_members(group_id) - except Exception as e: - logger.error(e) - error_msg = 'Internal Server Error' - return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - - for m in members: - member_info = self._get_address_book_group_memeber_info(request, - m, avatar_size) - ret_members.append(member_info) - ret_dict['groups'] = ret_groups - ret_dict['members'] = ret_members if return_ancestors: # get ancestor groups and remove last group which is self diff --git a/seahub/api2/endpoints/admin/group_members.py b/seahub/api2/endpoints/admin/group_members.py index 79135dfe91..8887a9aa57 100644 --- a/seahub/api2/endpoints/admin/group_members.py +++ b/seahub/api2/endpoints/admin/group_members.py @@ -45,18 +45,33 @@ class AdminGroupMembers(APIView): return api_error(status.HTTP_404_NOT_FOUND, error_msg) try: - avatar_size = int(request.GET.get('avatar_size', - AVATAR_DEFAULT_SIZE)) + avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE)) except ValueError: avatar_size = AVATAR_DEFAULT_SIZE try: - members = ccnet_api.get_group_members(group_id) + page = int(request.GET.get('page', '1')) + per_page = int(request.GET.get('per_page', '100')) + except ValueError: + page = 1 + per_page = 100 + + start = (page - 1) * per_page + limit = per_page + 1 + + try: + members = ccnet_api.get_group_members(group_id, start, limit) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) + if len(members) > per_page: + members = members[:per_page] + has_next_page = True + else: + has_next_page = False + group_members_info = [] for m in members: member_info = get_group_member_info(request, group_id, m.user_name, avatar_size) @@ -65,9 +80,12 @@ class AdminGroupMembers(APIView): group_members = { 'group_id': group_id, 'group_name': group.group_name, - 'members': group_members_info + 'members': group_members_info, + 'page_info': { + 'has_next_page': has_next_page, + 'current_page': page + } } - return Response(group_members) def post(self, request, group_id): diff --git a/seahub/api2/endpoints/group_members.py b/seahub/api2/endpoints/group_members.py index 7be9b80236..8f364001c5 100644 --- a/seahub/api2/endpoints/group_members.py +++ b/seahub/api2/endpoints/group_members.py @@ -45,13 +45,23 @@ class GroupMembers(APIView): except ValueError: avatar_size = AVATAR_DEFAULT_SIZE + try: + page = int(request.GET.get('page', '1')) + per_page = int(request.GET.get('per_page', '100')) + except ValueError: + page = 1 + per_page = 100 + + start = (page - 1) * per_page + limit = per_page + try: # only group member can get info of all group members if not is_group_member(group_id, request.user.username): error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - members = ccnet_api.get_group_members(group_id) + members = ccnet_api.get_group_members(group_id, start, limit) except SearpcError as e: logger.error(e) From 81bc3a983b5ba5c214d0ab0bd03eff3a29e254d4 Mon Sep 17 00:00:00 2001 From: llj Date: Mon, 26 Oct 2020 16:53:14 +0800 Subject: [PATCH 07/18] [seafile-js] updated the version --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 82288860b7..205a890545 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,7 +44,7 @@ "react-responsive": "^6.1.2", "react-select": "^2.4.1", "reactstrap": "^6.4.0", - "seafile-js": "0.2.158", + "seafile-js": "0.2.159", "socket.io-client": "^2.2.0", "sw-precache-webpack-plugin": "0.11.4", "unified": "^7.0.0", From 962495b0264d63e56c14be13d753ff3936df964a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=81=A5=E8=BE=89?= <40563566+jianhw@users.noreply.github.com> Date: Mon, 26 Oct 2020 17:18:05 +0800 Subject: [PATCH 08/18] repair email send error msg (#4705) --- seahub/api2/endpoints/invitations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seahub/api2/endpoints/invitations.py b/seahub/api2/endpoints/invitations.py index 8354d1db49..c4eb0ea51e 100644 --- a/seahub/api2/endpoints/invitations.py +++ b/seahub/api2/endpoints/invitations.py @@ -140,7 +140,6 @@ class InvitationsBatchView(APIView): i = Invitation.objects.add(inviter=request.user.username, accepter=accepter) - result['success'].append(i.to_dict()) m = i.send_to(email=accepter) if m.status != STATUS.sent: @@ -148,5 +147,7 @@ class InvitationsBatchView(APIView): 'email': accepter, 'error_msg': _('Failed to send email, email service is not properly configured, please contact administrator.'), }) + else: + result['success'].append(i.to_dict()) return Response(result) From d607704e80daf2bfc824652b892f7bc021e830d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=81=A5=E8=BE=89?= <40563566+jianhw@users.noreply.github.com> Date: Wed, 28 Oct 2020 14:47:57 +0800 Subject: [PATCH 09/18] optimize delete expired invitations (#4706) --- frontend/src/pages/sys-admin/invitations/invitations.js | 2 +- seahub/invitations/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/sys-admin/invitations/invitations.js b/frontend/src/pages/sys-admin/invitations/invitations.js index 42f35534fd..fcb228b7f2 100644 --- a/frontend/src/pages/sys-admin/invitations/invitations.js +++ b/frontend/src/pages/sys-admin/invitations/invitations.js @@ -275,7 +275,7 @@ class Invitations extends Component { deleteItemInBatch = () => { seafileAPI.sysAdminDeleteExpiredInvitations().then(res => { const prevItems = this.state.items; - const items = this.state.items.filter(item => !item.is_expired); + const items = this.state.items.filter(item => !item.is_expired || item.accept_time); if (items.length < prevItems.length) { this.setState({ items: items diff --git a/seahub/invitations/models.py b/seahub/invitations/models.py index 33caf20060..a04e5b380f 100644 --- a/seahub/invitations/models.py +++ b/seahub/invitations/models.py @@ -30,7 +30,7 @@ class InvitationManager(models.Manager): self).filter(inviter=inviter).order_by('-invite_time') def delete_all_expire_invitation(self): - super(InvitationManager, self).filter(expire_time__lte=timezone.now()).delete() + super(InvitationManager, self).filter(expire_time__lte=timezone.now(), accept_time__isnull=True).delete() def get_by_token(self, token): qs = self.filter(token=token) From d1ad56df63b381baa20690d0a3879c4b22ca6d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=81=A5=E8=BE=89?= <40563566+jianhw@users.noreply.github.com> Date: Sat, 31 Oct 2020 17:13:36 +0800 Subject: [PATCH 10/18] fix get file history (#4707) --- seahub/api2/endpoints/file_history.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/seahub/api2/endpoints/file_history.py b/seahub/api2/endpoints/file_history.py index 94ac02faa9..64c75a2a1c 100644 --- a/seahub/api2/endpoints/file_history.py +++ b/seahub/api2/endpoints/file_history.py @@ -133,15 +133,15 @@ class FileHistoryView(APIView): return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) result = [] + present_time = datetime.utcnow() for commit in file_revisions: - present_time = datetime.utcnow() history_time = datetime.utcfromtimestamp(commit.ctime) - if (keep_days == -1) or ((present_time - history_time).days < keep_days): - info = get_file_history_info(commit, avatar_size) - info['path'] = path - result.append(info) - - next_start_commit = next_start_commit if result else False + if (keep_days != -1) and ((present_time - history_time).days > keep_days): + next_start_commit = False + break + info = get_file_history_info(commit, avatar_size) + info['path'] = path + result.append(info) return Response({ "data": result, From 2248ebc50b07c2a1a6efb926dc084790eccbc2a8 Mon Sep 17 00:00:00 2001 From: lian Date: Mon, 2 Nov 2020 13:50:40 +0800 Subject: [PATCH 11/18] update search user logic when login via shib (#4708) Co-authored-by: lian --- thirdpart/shibboleth/backends.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/thirdpart/shibboleth/backends.py b/thirdpart/shibboleth/backends.py index 9154c7376d..47e15f184b 100644 --- a/thirdpart/shibboleth/backends.py +++ b/thirdpart/shibboleth/backends.py @@ -1,5 +1,4 @@ from django.conf import settings -from django.db import connection from seaserv import ccnet_api from seahub.auth.backends import RemoteUserBackend @@ -7,6 +6,7 @@ from seahub.base.accounts import User from registration.models import ( notify_admins_on_activate_request, notify_admins_on_register_complete) + class ShibbolethRemoteUserBackend(RemoteUserBackend): """ This backend is to be used in conjunction with the ``RemoteUserMiddleware`` @@ -49,6 +49,9 @@ class ShibbolethRemoteUserBackend(RemoteUserBackend): if not local_ccnet_users: local_ccnet_users = ccnet_api.search_emailusers('LDAP', username, -1, -1) + if username not in [item.email for item in local_ccnet_users]: + local_ccnet_users = [] + if not local_ccnet_users: if self.create_unknown_user: user = User.objects.create_user( From 18d44452b4b40dcc749009254e1a80b18f0b82d8 Mon Sep 17 00:00:00 2001 From: lian Date: Thu, 5 Nov 2020 15:12:59 +0800 Subject: [PATCH 12/18] update admin import dingtalk user (#4712) Co-authored-by: lian --- seahub/api2/endpoints/admin/dingtalk.py | 37 +++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/seahub/api2/endpoints/admin/dingtalk.py b/seahub/api2/endpoints/admin/dingtalk.py index 400af0a541..54ab6a3bdf 100644 --- a/seahub/api2/endpoints/admin/dingtalk.py +++ b/seahub/api2/endpoints/admin/dingtalk.py @@ -38,6 +38,7 @@ DEPARTMENT_OWNER = 'system admin' logger = logging.getLogger(__name__) + def update_dingtalk_user_info(email, name, contact_email, avatar_url): # make sure the contact_email is unique @@ -57,17 +58,19 @@ def update_dingtalk_user_info(email, name, contact_email, avatar_url): except Exception as e: logger.error(e) - try: - image_name = 'dingtalk_avatar' - image_file = requests.get(avatar_url).content - avatar = Avatar.objects.filter(emailuser=email, primary=True).first() - avatar = avatar or Avatar(emailuser=email, primary=True) - avatar_file = ContentFile(image_file) - avatar_file.name = image_name - avatar.avatar = avatar_file - avatar.save() - except Exception as e: - logger.error(e) + if avatar_url: + try: + image_name = 'dingtalk_avatar' + image_file = requests.get(avatar_url).content + avatar = Avatar.objects.filter(emailuser=email, primary=True).first() + avatar = avatar or Avatar(emailuser=email, primary=True) + avatar_file = ContentFile(image_file) + avatar_file.name = image_name + avatar.avatar = avatar_file + avatar.save() + except Exception as e: + logger.error(e) + class AdminDingtalkDepartments(APIView): @@ -213,8 +216,10 @@ class AdminDingtalkUsersBatch(APIView): }) try: - update_dingtalk_user_info(email, user.get('name'), - user.get('contact_email'), user.get('avatar')) + update_dingtalk_user_info(email, + user.get('name'), + user.get('contact_email'), + user.get('avatar')) except Exception as e: logger.error(e) @@ -418,8 +423,10 @@ class AdminDingtalkDepartmentsImport(APIView): failed.append(failed_msg) try: - update_dingtalk_user_info(email, api_user.get('name'), - api_user.get('contact_email'), api_user.get('avatar')) + update_dingtalk_user_info(email, + api_user.get('name'), + api_user.get('contact_email'), + api_user.get('avatar')) except Exception as e: logger.error(e) From 407061e743af2adb951c6f70489288584de94101 Mon Sep 17 00:00:00 2001 From: lian Date: Mon, 9 Nov 2020 21:11:04 +0800 Subject: [PATCH 13/18] send email to contact_email (#4714) Co-authored-by: lian --- seahub/api2/endpoints/admin/users.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/seahub/api2/endpoints/admin/users.py b/seahub/api2/endpoints/admin/users.py index ff51ae71e4..49ab80037d 100644 --- a/seahub/api2/endpoints/admin/users.py +++ b/seahub/api2/endpoints/admin/users.py @@ -607,7 +607,10 @@ class AdminUsers(APIView): c = {'user': request.user.username, 'email': email, 'password': password} try: send_html_email(_('You are invited to join %s') % get_site_name(), - 'sysadmin/user_add_email.html', c, None, [email]) + 'sysadmin/user_add_email.html', + c, + None, + [email2contact_email(email)]) add_user_tip = _('Successfully added user %(user)s. An email notification has been sent.') % {'user': email} except Exception as e: logger.error(str(e)) @@ -927,7 +930,10 @@ class AdminUser(APIView): if user_obj.is_active and IS_EMAIL_CONFIGURED: try: send_html_email(_(u'Your account on %s is activated') % get_site_name(), - 'sysadmin/user_activation_email.html', {'username': user_obj.email}, None, [user_obj.email]) + 'sysadmin/user_activation_email.html', + {'username': user_obj.email}, + None, + [email2contact_email(user_obj.email)]) update_status_tip = _('Edit succeeded, an email has been sent.') except Exception as e: logger.error(e) From bbac14cc4a0c454b79277d47619ee7b6418c603a Mon Sep 17 00:00:00 2001 From: lian Date: Wed, 11 Nov 2020 09:56:40 +0800 Subject: [PATCH 14/18] fix bug of org admin change user active status (#4715) Co-authored-by: lian --- frontend/src/pages/org-admin/org-user-item.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/org-admin/org-user-item.js b/frontend/src/pages/org-admin/org-user-item.js index f4f43124bc..f52359e422 100644 --- a/frontend/src/pages/org-admin/org-user-item.js +++ b/frontend/src/pages/org-admin/org-user-item.js @@ -77,16 +77,16 @@ class UserItem extends React.Component { } changeStatus = (st) => { - let statusCode; + let isActive; if (st == 'active') { - statusCode = 1; + isActive = 'true'; } else { - statusCode = 0; + isActive = 'false'; } - seafileAPI.orgAdminChangeOrgUserStatus(this.props.user.id, statusCode).then(res => { + seafileAPI.orgAdminChangeOrgUserStatus(orgID, this.props.user.email, isActive).then(res => { this.setState({ - currentStatus: statusCode == 1 ? 'active' : 'inactive', + currentStatus: isActive == 'true' ? 'active' : 'inactive', highlight: false, showMenu: false, }); From abb57964b953b9d92f4c2a75d2e28c28701fe2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E9=A1=BA=E5=BC=BA?= Date: Wed, 11 Nov 2020 11:06:26 +0800 Subject: [PATCH 15/18] Optimize file uploader (#4716) * optimize file uploader * optimize file uploader * update resumablejs version --- frontend/package-lock.json | 6 ++--- frontend/package.json | 2 +- .../components/file-uploader/file-uploader.js | 24 ++++++++++++------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cf01488777..54663faa42 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -148,9 +148,9 @@ } }, "@seafile/resumablejs": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@seafile/resumablejs/-/resumablejs-1.1.15.tgz", - "integrity": "sha512-DJPhjBRLENONdDNaaKRckWWtwXvoqfJcRdSk01FbjmZ3DWpbIwebd/vfuB4qJwm0R2j2qzdEVhgxe6wDYS/n9A==" + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@seafile/resumablejs/-/resumablejs-1.1.16.tgz", + "integrity": "sha512-8rBbmAEuuwOAGHYGCtEzpx+bxAcGS+V30otMmhRe7bPAdh4E57RWgCa8x7pkzHGFlY1t5d+ILz1gojvPVMYQig==" }, "@seafile/seafile-calendar": { "version": "0.0.12", diff --git a/frontend/package.json b/frontend/package.json index 205a890545..7ec22d1188 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,7 @@ "dependencies": { "@reach/router": "^1.2.0", "@seafile/react-image-lightbox": "0.0.1", - "@seafile/resumablejs": "^1.1.15", + "@seafile/resumablejs": "1.1.16", "@seafile/seafile-calendar": "0.0.12", "@seafile/seafile-editor": "^0.2.84", "MD5": "^1.3.0", diff --git a/frontend/src/components/file-uploader/file-uploader.js b/frontend/src/components/file-uploader/file-uploader.js index a65afbf8c2..ee1f66c5ac 100644 --- a/frontend/src/components/file-uploader/file-uploader.js +++ b/frontend/src/components/file-uploader/file-uploader.js @@ -388,6 +388,18 @@ class FileUploader extends React.Component { this.setState({uploadFileList: uploadFileList}); } + getFileServerErrorMessage = (key) => { + const errorMessage = { + 'File locked by others.': gettext('File locked by others.'), // 403 + 'Invalid filename.': gettext('Invalid filename.'), // 440 + 'File already exists.': gettext('File already exists.'), // 441 + 'File size is too large.': gettext('File size is too large.'), // 442 + 'Out of quota.': gettext('Out of quota.'), // 443 + 'Internal error.': gettext('Internal Server Error'), // 500 + } + return errorMessage[key] || key; + } + onFileError = (resumableFile, message) => { let error = ''; if (!message) { @@ -396,13 +408,7 @@ class FileUploader extends React.Component { // eg: '{"error": "Internal error" \n }' let errorMessage = message.replace(/\n/g, ''); errorMessage = JSON.parse(errorMessage); - error = errorMessage.error; - if (error === 'File locked by others.') { - error = gettext('File is locked by others.'); - } - if (error === 'Internal error.') { - error = gettext('Internal Server Error'); - } + error = this.getFileServerErrorMessage(errorMessage.error); } let uploadFileList = this.state.uploadFileList.map(item => { @@ -489,12 +495,12 @@ class FileUploader extends React.Component { onFileUpload = () => { this.uploadInput.current.removeAttribute('webkitdirectory'); - this.uploadInput.current.click(); + this.uploadInput.current.click(); } onFolderUpload = () => { this.uploadInput.current.setAttribute('webkitdirectory', 'webkitdirectory'); - this.uploadInput.current.click(); + this.uploadInput.current.click(); } onDragStart = () => { From f323d08eca437533713cce00d3e3a5be3c5fc37f Mon Sep 17 00:00:00 2001 From: lian Date: Wed, 18 Nov 2020 11:24:31 +0800 Subject: [PATCH 16/18] fix bug when get oauth_redirect in session (#4721) Co-authored-by: lian --- seahub/oauth/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/seahub/oauth/views.py b/seahub/oauth/views.py index 7892c4212d..f6e10024d8 100644 --- a/seahub/oauth/views.py +++ b/seahub/oauth/views.py @@ -183,8 +183,7 @@ def oauth_callback(request): # update user's profile name = user_info['name'] if 'name' in user_info else '' - contact_email = user_info['contact_email'] if \ - 'contact_email' in user_info else '' + contact_email = user_info['contact_email'] if 'contact_email' in user_info else '' profile = Profile.objects.get_profile_by_user(email) if not profile: @@ -202,6 +201,6 @@ def oauth_callback(request): api_token = get_api_token(request) # redirect user to home page - response = HttpResponseRedirect(request.session['oauth_redirect']) + response = HttpResponseRedirect(request.session.get('oauth_redirect', '/')) response.set_cookie('seahub_auth', email + '@' + api_token.key) return response From 87a80eadc5b8cc48760c52aaaecc05ab0852e045 Mon Sep 17 00:00:00 2001 From: llj Date: Thu, 19 Nov 2020 15:58:43 +0800 Subject: [PATCH 17/18] [markdown] improved 'print' (#4722) * when the right panel is open, make the print be ok * print all the pages in Firefox --- media/css/print_for_md_file_view.css | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/media/css/print_for_md_file_view.css b/media/css/print_for_md_file_view.css index dc059f0f7f..632232da2a 100644 --- a/media/css/print_for_md_file_view.css +++ b/media/css/print_for_md_file_view.css @@ -8,3 +8,14 @@ .editor { border: none!important; } +.seafile-editor-main { + display: block!important; +} +.seafile-editor-main-panel { + display: block!important; + width: 100%!important; +} +.seafile-editor-resize, +.seafile-editor-side-panel { + display: none; +} From cae1204ab1701417659302f334b3e72d5453df11 Mon Sep 17 00:00:00 2001 From: lian Date: Fri, 20 Nov 2020 10:08:49 +0800 Subject: [PATCH 18/18] support LIBRARY_TEMPLATES (#4723) Co-authored-by: lian --- .../components/dialog/create-repo-dialog.js | 25 ++++++++++++++++++- frontend/src/utils/constants.js | 1 + seahub/handlers.py | 6 +---- seahub/templates/base_for_react.html | 8 ++++++ seahub/views/__init__.py | 1 + 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/dialog/create-repo-dialog.js b/frontend/src/components/dialog/create-repo-dialog.js index 485084a811..b6e6f07441 100644 --- a/frontend/src/components/dialog/create-repo-dialog.js +++ b/frontend/src/components/dialog/create-repo-dialog.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import Select from 'react-select'; import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Form, FormGroup, Label, Alert } from 'reactstrap'; -import { gettext, enableEncryptedLibrary, repoPasswordMinLength, storages } from '../../utils/constants'; +import { gettext, enableEncryptedLibrary, repoPasswordMinLength, storages, libraryTemplates } from '../../utils/constants'; const propTypes = { libraryType: PropTypes.string.isRequired, @@ -22,6 +22,7 @@ class CreateRepoDialog extends React.Component { errMessage: '', permission: 'rw', storage_id: storages.length ? storages[0].id : '', + library_template: libraryTemplates.length ? libraryTemplates[0] : '', isSubmitBtnActive: false, }; this.newInput = React.createRef(); @@ -121,6 +122,10 @@ class CreateRepoDialog extends React.Component { this.setState({storage_id: selectedItem.value}); } + handlelibraryTemplatesInputChange = (selectedItem) => { + this.setState({library_template: selectedItem.value}); + } + onEncrypted = (e) => { let isChecked = e.target.checked; this.setState({ @@ -162,6 +167,11 @@ class CreateRepoDialog extends React.Component { repo.storage_id = storage_id; } + const library_template = this.state.library_template; + if (library_template) { + repo.library_template = library_template; + } + return repo; } @@ -192,6 +202,19 @@ class CreateRepoDialog extends React.Component { /> )} + + {libraryTemplates.length > 0 && ( + + +