diff --git a/frontend/src/pages/sys-admin/orgs/org-users.js b/frontend/src/pages/sys-admin/orgs/org-users.js
index af80de93cd..b2e921a770 100644
--- a/frontend/src/pages/sys-admin/orgs/org-users.js
+++ b/frontend/src/pages/sys-admin/orgs/org-users.js
@@ -8,6 +8,7 @@ import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import Loading from '../../../components/loading';
import SysAdminUserStatusEditor from '../../../components/select-editor/sysadmin-user-status-editor';
+import SysAdminUserMembershipEditor from '../../../components/select-editor/sysadmin-user-membership-editor';
import SysAdminAddUserDialog from '../../../components/dialog/sysadmin-dialog/sysadmin-add-user-dialog';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
import OpMenu from '../../../components/dialog/op-menu';
@@ -50,9 +51,10 @@ class Content extends Component {
{gettext('Name')} |
- {gettext('Status')} |
- {gettext('Space Used')} |
- {gettext('Created At')}{' / '}{gettext('Last Login')} |
+ {gettext('Status')} |
+ {gettext('Membership')} |
+ {gettext('Space Used')} |
+ {gettext('Created At')}{' / '}{gettext('Last Login')} |
{/* Operations */} |
@@ -65,6 +67,7 @@ class Content extends Component {
onFreezedItem={this.onFreezedItem}
onUnfreezedItem={this.onUnfreezedItem}
updateStatus={this.props.updateStatus}
+ updateMembership={this.props.updateMembership}
deleteUser={this.props.deleteUser}
/>);
})}
@@ -146,6 +149,10 @@ class Item extends Component {
this.props.updateStatus(this.props.item.email, statusValue);
}
+ updateMembership= (membershipValue) => {
+ this.props.updateMembership(this.props.item.email, membershipValue);
+ }
+
deleteUser = () => {
const { item } = this.props;
this.props.deleteUser(item.org_id, item.email);
@@ -195,6 +202,15 @@ class Item extends Component {
onStatusChanged={this.updateStatus}
/>
+
+
+ |
{`${Utils.bytesToSize(item.quota_usage)} / ${item.quota_total > 0 ? Utils.bytesToSize(item.quota_total) : '--'}`} |
{moment(item.create_time).format('YYYY-MM-DD HH:mm:ss')}{' / '}{item.last_login ? moment(item.last_login).fromNow() : '--'}
@@ -311,6 +327,22 @@ class OrgUsers extends Component {
});
}
+ updateMembership = (email, membershipValue) => {
+ const isOrgStaff = membershipValue == 'is_org_staff';
+ seafileAPI.sysAdminUpdateOrgUser(this.props.orgID, email, 'is_org_staff', isOrgStaff).then(res => {
+ let newUserList = this.state.userList.map(item => {
+ if (item.email == email) {
+ item.is_org_staff = res.data.is_org_staff;
+ }
+ return item;
+ });
+ this.setState({userList: newUserList});
+ }).catch((error) => {
+ let errMessage = Utils.getErrorMsg(error);
+ toaster.danger(errMessage);
+ });
+ }
+
render() {
const { isAddUserDialogOpen, orgName } = this.state;
return (
@@ -331,6 +363,7 @@ class OrgUsers extends Component {
errorMsg={this.state.errorMsg}
items={this.state.userList}
updateStatus={this.updateStatus}
+ updateMembership={this.updateMembership}
deleteUser={this.deleteUser}
/>
diff --git a/seahub/api2/endpoints/admin/invitations.py b/seahub/api2/endpoints/admin/invitations.py
index da98f7a05e..d6d515bb57 100644
--- a/seahub/api2/endpoints/admin/invitations.py
+++ b/seahub/api2/endpoints/admin/invitations.py
@@ -89,7 +89,7 @@ class AdminInvitations(APIView):
data['invite_type'] = invitation.invite_type
data['invite_time'] = datetime_to_isoformat_timestr(invitation.invite_time)
- data['accept_time'] = datetime_to_isoformat_timestr(invitation.accept_time)
+ data['accept_time'] = datetime_to_isoformat_timestr(invitation.accept_time) if invitation.accept_time else ''
data['expire_time'] = datetime_to_isoformat_timestr(invitation.expire_time)
data['is_expired'] = invitation.is_expired()
diff --git a/seahub/api2/endpoints/admin/org_users.py b/seahub/api2/endpoints/admin/org_users.py
index 6c8bde1dda..03c38c77e8 100644
--- a/seahub/api2/endpoints/admin/org_users.py
+++ b/seahub/api2/endpoints/admin/org_users.py
@@ -61,6 +61,8 @@ def get_org_user_info(org_id, user_obj):
if last_login:
user_info['last_login'] = datetime_to_isoformat_timestr(last_login)
+ user_info['is_org_staff'] = True if ccnet_api.is_org_staff(org_id, email) == 1 else False
+
return user_info
def check_org_user(func):
@@ -352,6 +354,28 @@ class AdminOrgUser(APIView):
seafile_api.set_org_user_quota(org_id, email, user_quota)
+ # update is_org_staff
+ is_org_staff = request.data.get("is_org_staff", '')
+ if is_org_staff:
+
+ is_org_staff = is_org_staff.lower()
+ if is_org_staff not in ('true', 'false'):
+ error_msg = 'is_org_staff invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ if is_org_staff == 'true':
+ if ccnet_api.is_org_staff(org_id, email):
+ error_msg = '%s is already organization staff.' % email
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ ccnet_api.set_org_staff(org_id, email)
+ else:
+ if not ccnet_api.is_org_staff(org_id, email):
+ error_msg = '%s is not organization staff.' % email
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ ccnet_api.unset_org_staff(org_id, email)
+
user_info = get_org_user_info(org_id, user)
user_info['active'] = user.is_active
return Response(user_info)
diff --git a/seahub/api2/endpoints/admin/users.py b/seahub/api2/endpoints/admin/users.py
index 9dc1c24928..d21aa647ef 100644
--- a/seahub/api2/endpoints/admin/users.py
+++ b/seahub/api2/endpoints/admin/users.py
@@ -689,6 +689,9 @@ class AdminUsers(APIView):
admin_operation.send(sender=None, admin_name=request.user.username,
operation=USER_ADD, detail=admin_op_detail)
+ if config.FORCE_PASSWORD_CHANGE:
+ UserOptions.objects.set_force_passwd_change(email)
+
return Response(user_info)
diff --git a/seahub/api2/endpoints/repo_share_invitations.py b/seahub/api2/endpoints/repo_share_invitations.py
index e45d636e77..c1310f23f2 100644
--- a/seahub/api2/endpoints/repo_share_invitations.py
+++ b/seahub/api2/endpoints/repo_share_invitations.py
@@ -1,7 +1,9 @@
# Copyright (c) 2012-2019 Seafile Ltd.
import logging
+from datetime import timedelta
+from django.utils import timezone
from django.utils.translation import ugettext as _
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
@@ -19,6 +21,7 @@ from seahub.base.accounts import User
from seahub.utils import is_valid_email
from seahub.invitations.models import Invitation, RepoShareInvitation
from seahub.invitations.utils import block_accepter
+from seahub.invitations.settings import INVITATIONS_TOKEN_AGE
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE, GUEST_USER
from seahub.share.utils import is_repo_admin
from seahub.utils import is_org_context
@@ -175,6 +178,8 @@ class RepoShareInvitationsBatchView(APIView):
if invitation_queryset.filter(accepter=accepter).exists():
invitation = invitation_queryset.filter(accepter=accepter)[0]
+ invitation.expire_time = timezone.now() + timedelta(hours=int(INVITATIONS_TOKEN_AGE))
+ invitation.save()
else:
invitation = Invitation.objects.add(
inviter=request.user.username, accepter=accepter)
diff --git a/seahub/api2/endpoints/repo_trash.py b/seahub/api2/endpoints/repo_trash.py
index 7952d2b747..232a1ca8f3 100644
--- a/seahub/api2/endpoints/repo_trash.py
+++ b/seahub/api2/endpoints/repo_trash.py
@@ -25,6 +25,7 @@ from constance import config
logger = logging.getLogger(__name__)
+
class RepoTrash(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
@@ -97,8 +98,81 @@ class RepoTrash(APIView):
try:
# a list will be returned, with at least 1 item in it
# the last item is not a deleted entry, and it contains an attribute named 'scan_stat'
- deleted_entries = seafile_api.get_deleted(repo_id,
- show_days, path, scan_stat)
+ deleted_entries = seafile_api.get_deleted(repo_id, show_days, path, scan_stat)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ scan_stat = deleted_entries[-1].scan_stat
+ more = True if scan_stat is not None else False
+
+ items = []
+ if len(deleted_entries) > 1:
+ entries_without_scan_stat = deleted_entries[0:-1]
+
+ # sort entry by delete time
+ entries_without_scan_stat.sort(
+ key=lambda x: x.delete_time, reverse=True)
+
+ for item in entries_without_scan_stat:
+ item_info = self.get_item_info(item)
+ items.append(item_info)
+
+ result = {
+ 'data': items,
+ 'more': more,
+ 'scan_stat': scan_stat,
+ }
+
+ return Response(result)
+
+ def post(self, request, repo_id, format=None):
+ """ Return deleted files/dirs of a repo/folder
+
+ Permission checking:
+ 1. all authenticated user can perform this action.
+ """
+
+ # argument check
+ path = request.data.get('path', '/')
+
+ # 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)
+
+ try:
+ dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
+ except SearpcError as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ if not dir_id:
+ error_msg = 'Folder %s not found.' % path
+ return api_error(status.HTTP_404_NOT_FOUND, error_msg)
+
+ # permission check
+ if check_folder_permission(request, repo_id, path) is None:
+ error_msg = 'Permission denied.'
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+
+ try:
+ show_days = int(request.data.get('show_days', '0'))
+ except ValueError:
+ show_days = 0
+
+ if show_days < 0:
+ error_msg = 'show_days invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
+ scan_stat = request.data.get('scan_stat', None)
+ try:
+ # a list will be returned, with at least 1 item in it
+ # the last item is not a deleted entry, and it contains an attribute named 'scan_stat'
+ deleted_entries = seafile_api.get_deleted(repo_id, show_days, path, scan_stat)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -169,8 +243,8 @@ class RepoTrash(APIView):
seafile_api.clean_up_repo_history(repo_id, keep_days)
org_id = None if not request.user.org else request.user.org.org_id
clean_up_repo_trash.send(sender=None, org_id=org_id,
- operator=username, repo_id=repo_id, repo_name=repo.name,
- repo_owner=repo_owner, days=keep_days)
+ operator=username, repo_id=repo_id, repo_name=repo.name,
+ repo_owner=repo_owner, days=keep_days)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
diff --git a/seahub/api2/endpoints/webdav_secret.py b/seahub/api2/endpoints/webdav_secret.py
index 76e5258931..a83b26d6c1 100644
--- a/seahub/api2/endpoints/webdav_secret.py
+++ b/seahub/api2/endpoints/webdav_secret.py
@@ -7,6 +7,8 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
+from django.utils.translation import ugettext as _
+
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
@@ -43,6 +45,9 @@ class WebdavSecretView(APIView):
username = request.user.username
secret = request.data.get("secret", None)
+ if len(secret) >= 30:
+ return api_error(status.HTTP_400_BAD_REQUEST,
+ _("Length of WebDav password should be less then 30."))
if secret:
encoded = aes.encode(secret)
diff --git a/seahub/onlyoffice/utils.py b/seahub/onlyoffice/utils.py
index 8054c2e719..e071965bce 100644
--- a/seahub/onlyoffice/utils.py
+++ b/seahub/onlyoffice/utils.py
@@ -13,7 +13,7 @@ from seaserv import seafile_api
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.utils import get_file_type_and_ext, gen_file_get_url, \
- get_site_scheme_and_netloc, normalize_cache_key
+ get_site_scheme_and_netloc, encrypt_with_sha1
from seahub.utils.file_op import if_locked_by_online_office
from seahub.settings import ENABLE_WATERMARK
@@ -25,9 +25,8 @@ logger = logging.getLogger('onlyoffice')
def generate_onlyoffice_cache_key(repo_id, file_path):
- prefix = "ONLYOFFICE_"
- value = "%s_%s" % (repo_id, file_path)
- return normalize_cache_key(value, prefix)
+
+ return "ONLYOFFICE_{}_{}".format(repo_id, encrypt_with_sha1(file_path))
def get_onlyoffice_dict(request, username, repo_id, file_path, file_id='',
diff --git a/seahub/two_factor/models/static.py b/seahub/two_factor/models/static.py
index 19f898e70c..0c3193f8f9 100644
--- a/seahub/two_factor/models/static.py
+++ b/seahub/two_factor/models/static.py
@@ -70,4 +70,4 @@ class StaticToken(models.Model):
:rtype: str
"""
- return b32encode(urandom(5)).lower()
+ return b32encode(urandom(5)).lower().decode()
diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py
index 46be5789f8..4bb58705fe 100644
--- a/seahub/utils/__init__.py
+++ b/seahub/utils/__init__.py
@@ -1387,3 +1387,8 @@ def is_valid_org_id(org_id):
return True
else:
return False
+
+
+def encrypt_with_sha1(origin_str):
+
+ return hashlib.sha1(origin_str.encode()).hexdigest()
diff --git a/seahub/utils/hasher.py b/seahub/utils/hasher.py
index f9f7e9cb04..ac5a490b8f 100644
--- a/seahub/utils/hasher.py
+++ b/seahub/utils/hasher.py
@@ -51,4 +51,5 @@ class AESPasswordHasher:
raise AESPasswordDecodeError
data = data.encode('utf-8')
+ data += b'='*4
return DecodeAES(self.cipher, data)
diff --git a/seahub/utils/timeutils.py b/seahub/utils/timeutils.py
index 47d9c66c5b..a0cc505090 100644
--- a/seahub/utils/timeutils.py
+++ b/seahub/utils/timeutils.py
@@ -12,6 +12,7 @@ logger = logging.getLogger(__name__)
# https://docs.djangoproject.com/en/1.11/ref/utils/#django.utils.timezone.get_current_timezone
current_timezone = get_current_timezone()
+
def dt(value):
"""Convert 32/64 bits timestamp to datetime object.
"""
@@ -21,6 +22,7 @@ def dt(value):
# TODO: need a better way to handle 64 bits timestamp.
return datetime.datetime.utcfromtimestamp(value/1000000)
+
def value_to_db_datetime(value):
if value is None:
return None
@@ -35,6 +37,7 @@ def value_to_db_datetime(value):
# MySQL doesn't support microseconds
return six.text_type(value.replace(microsecond=0))
+
def utc_to_local(dt):
# change from UTC timezone to current seahub timezone
tz = timezone.get_default_timezone()
@@ -42,6 +45,7 @@ def utc_to_local(dt):
local = timezone.make_naive(utc, tz)
return local
+
def timestamp_to_isoformat_timestr(timestamp):
try:
min_ts = -(1 << 31)
@@ -58,9 +62,13 @@ def timestamp_to_isoformat_timestr(timestamp):
logger.error(e)
return ''
+
# https://pypi.org/project/pytz/
def datetime_to_isoformat_timestr(datetime):
+ if not datetime:
+ return ''
+
from django.utils.timezone import make_naive, is_aware
if is_aware(datetime):
datetime = make_naive(datetime)
@@ -76,6 +84,7 @@ def datetime_to_isoformat_timestr(datetime):
logger.error(e)
return ''
+
def utc_datetime_to_isoformat_timestr(utc_datetime):
try:
# The second way of building a localized time is by converting an existing
@@ -88,6 +97,7 @@ def utc_datetime_to_isoformat_timestr(utc_datetime):
logger.error(e)
return ''
+
def datetime_to_timestamp(datetime_obj):
epoch = datetime.datetime(1970, 1, 1)
local = utc_to_local(datetime_obj)
|