diff --git a/frontend/src/components/user-select.js b/frontend/src/components/user-select.js
index 98087147c7..d9e007a249 100644
--- a/frontend/src/components/user-select.js
+++ b/frontend/src/components/user-select.js
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/async';
import { seafileAPI } from '../utils/seafile-api';
-import { gettext, enableShowContactEmailWhenSearchUser } from '../utils/constants';
+import { gettext, enableShowContactEmailWhenSearchUser, enableShowLoginIDWhenSearchUser } from '../utils/constants';
import { Utils } from '../utils/utils';
import toaster from './toast';
import { UserSelectStyle } from './common/select';
@@ -54,24 +54,21 @@ class UserSelect extends React.Component {
let obj = {};
obj.value = item.name;
obj.email = item.email;
- if (enableShowContactEmailWhenSearchUser) {
- obj.label = (
-
-

-
- {item.name}
- {item.contact_email}
-
+ obj.label = (enableShowContactEmailWhenSearchUser || enableShowLoginIDWhenSearchUser) ? (
+
+

+
+ {item.name}
+ {enableShowContactEmailWhenSearchUser && {item.contact_email}}
+ {enableShowLoginIDWhenSearchUser && {item.login_id}}
- );
- } else {
- obj.label = (
- <>
-

-
{item.name}
- >
- );
- }
+
+ ) : (
+
+
+ {item.name}
+
+ );
this.options.push(obj);
}
callback(this.options);
diff --git a/frontend/src/components/user-settings/user-basic-info-form.js b/frontend/src/components/user-settings/user-basic-info-form.js
index f9df710f0e..b3f623dda8 100644
--- a/frontend/src/components/user-settings/user-basic-info-form.js
+++ b/frontend/src/components/user-settings/user-basic-info-form.js
@@ -5,7 +5,8 @@ import { gettext } from '../../utils/constants';
const {
nameLabel,
enableUpdateUserInfo,
- enableUserSetContactEmail
+ enableUserSetContactEmail,
+ enableUserSetName
} = window.app.pageOptions;
class UserBasicInfoForm extends React.Component {
@@ -38,9 +39,10 @@ class UserBasicInfoForm extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
- let data = {
- name: this.state.name
- };
+ let data = {};
+ if (enableUserSetName) {
+ data.name = this.state.name;
+ }
if (enableUserSetContactEmail) {
data.contact_email = this.state.contactEmail;
}
@@ -60,7 +62,7 @@ class UserBasicInfoForm extends React.Component {
diff --git a/frontend/src/pages/sys-admin/admin-logs/operation-logs.js b/frontend/src/pages/sys-admin/admin-logs/operation-logs.js
index 6794fd94da..19972cbf75 100644
--- a/frontend/src/pages/sys-admin/admin-logs/operation-logs.js
+++ b/frontend/src/pages/sys-admin/admin-logs/operation-logs.js
@@ -104,6 +104,7 @@ class Item extends Component {
case 'group_delete': return gettext('Delete Group');
case 'user_add': return gettext('Add User');
case 'user_delete': return gettext('Delete User');
+ case 'user_migrate': return gettext('Migrate User');
default: return '';
}
};
@@ -183,6 +184,12 @@ class Item extends Component {
.replace('{user}', '
' + detail.email + '');
return detailText;
+ case 'user_migrate':
+ detailText = gettext('User migrate from {user_from} to {user_to}')
+ .replace('{user_from}', '
' + detail.from + '')
+ .replace('{user_to}', '
' + detail.to+ '');
+ return detailText;
+
default: return '';
}
};
diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js
index c8ef1eb3fa..5629de3b39 100644
--- a/frontend/src/utils/constants.js
+++ b/frontend/src/utils/constants.js
@@ -81,6 +81,7 @@ export const canInvitePeople = window.app.pageOptions.canInvitePeople;
export const canLockUnlockFile = window.app.pageOptions.canLockUnlockFile;
export const customNavItems = window.app.pageOptions.customNavItems;
export const enableShowContactEmailWhenSearchUser = window.app.pageOptions.enableShowContactEmailWhenSearchUser;
+export const enableShowLoginIDWhenSearchUser = window.app.pageOptions.enableShowLoginIDWhenSearchUser;
export const maxUploadFileSize = window.app.pageOptions.maxUploadFileSize;
export const maxNumberOfFilesForFileupload = window.app.pageOptions.maxNumberOfFilesForFileupload;
export const enableOCM = window.app.pageOptions.enableOCM;
diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js
index e5d83bbc8d..e676e4be5c 100644
--- a/frontend/src/utils/utils.js
+++ b/frontend/src/utils/utils.js
@@ -1237,6 +1237,8 @@ export const Utils = {
);
}
errorMsg = gettext('Permission denied');
+ } else if (error.response.status == 429) {
+ errorMsg = gettext('Too many requests');
} else if (error.response.data &&
error.response.data['error_msg']) {
errorMsg = error.response.data['error_msg'];
diff --git a/seahub/admin_log/models.py b/seahub/admin_log/models.py
index b577445d0a..b0373226a1 100644
--- a/seahub/admin_log/models.py
+++ b/seahub/admin_log/models.py
@@ -28,9 +28,12 @@ USER_ADD = 'user_add'
# 'user_delete': {'email': deleted_user}
USER_DELETE = 'user_delete'
+# 'user_migrate': {'from': from_user, 'to': to_user}
+USER_MIGRATE = 'user_migrate'
+
ADMIN_LOG_OPERATION_TYPE = (REPO_TRANSFER, REPO_DELETE,
GROUP_CREATE, GROUP_TRANSFER, GROUP_DELETE,
- USER_ADD, USER_DELETE)
+ USER_ADD, USER_DELETE, USER_MIGRATE)
class AdminLogManager(models.Manager):
diff --git a/seahub/api2/endpoints/admin/account.py b/seahub/api2/endpoints/admin/account.py
index a24626d40e..af3661cefe 100644
--- a/seahub/api2/endpoints/admin/account.py
+++ b/seahub/api2/endpoints/admin/account.py
@@ -13,6 +13,8 @@ from rest_framework.views import APIView
import seaserv
from seaserv import seafile_api, ccnet_threaded_rpc, ccnet_api
+from seahub.admin_log.models import USER_MIGRATE
+from seahub.admin_log.signals import admin_operation
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.serializers import AccountSerializer
from seahub.api2.throttling import UserRateThrottle
@@ -21,6 +23,7 @@ from seahub.base.accounts import User
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.profile.models import Profile, DetailedProfile
from seahub.institutions.models import Institution
+from seahub.share.models import UploadLinkShare, FileShare
from seahub.utils import is_valid_username, is_org_context
from seahub.utils.file_size import get_file_size_unit
from seahub.group.utils import is_group_member
@@ -107,6 +110,21 @@ class Account(APIView):
if from_user == g.creator_name:
ccnet_threaded_rpc.set_group_creator(g.id, to_user)
+ # reshare repo to links
+ try:
+ UploadLinkShare.objects.filter(username=from_user).update(username=to_user)
+ FileShare.objects.filter(username=from_user).update(username=to_user)
+ except Exception as e:
+ logger.error(e)
+ error_msg = 'Internal Server Error'
+ return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ admin_op_detail = {
+ "from": from_user,
+ "to": to_user
+ }
+ admin_operation.send(sender=None, admin_name=request.user.username,
+ operation=USER_MIGRATE, detail=admin_op_detail)
return Response({'success': True})
else:
return api_error(status.HTTP_400_BAD_REQUEST, 'op can only be migrate.')
diff --git a/seahub/api2/endpoints/admin/organizations.py b/seahub/api2/endpoints/admin/organizations.py
index 3a9d88fdcc..173129eb9f 100644
--- a/seahub/api2/endpoints/admin/organizations.py
+++ b/seahub/api2/endpoints/admin/organizations.py
@@ -12,6 +12,7 @@ from rest_framework import status
from seaserv import ccnet_api, seafile_api
from seahub.auth.utils import get_virtual_id_by_email
+from seahub.organizations.settings import ORG_MEMBER_QUOTA_DEFAULT
from seahub.utils import is_valid_email
from seahub.utils.file_size import get_file_size_unit
from seahub.utils.timeutils import timestamp_to_isoformat_timestr
@@ -233,6 +234,20 @@ class AdminOrganizations(APIView):
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
+
+ quota = request.data.get('quota', None)
+ if quota:
+ try:
+ quota_mb = int(quota)
+ quota = quota_mb * get_file_size_unit('MB')
+ seafile_api.set_org_quota(org_id, quota)
+ except ValueError as e:
+ logger.error(e)
+ return api_error(status.HTTP_400_BAD_REQUEST, "Quota is not valid")
+
+ if ORG_MEMBER_QUOTA_ENABLED:
+ member_limit = request.data.get('member_limit', ORG_MEMBER_QUOTA_DEFAULT)
+ OrgMemberQuota.objects.set_quota(org_id, member_limit)
org = ccnet_api.get_org_by_id(org_id)
try:
diff --git a/seahub/api2/endpoints/search_user.py b/seahub/api2/endpoints/search_user.py
index d4afbbbbb8..89e4df31f3 100644
--- a/seahub/api2/endpoints/search_user.py
+++ b/seahub/api2/endpoints/search_user.py
@@ -27,7 +27,7 @@ from seahub.contacts.models import Contact
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.settings import ENABLE_GLOBAL_ADDRESSBOOK, \
- ENABLE_SEARCH_FROM_LDAP_DIRECTLY
+ ENABLE_SEARCH_FROM_LDAP_DIRECTLY, ENABLE_SHOW_LOGIN_ID_WHEN_SEARCH_USER
logger = logging.getLogger(__name__)
@@ -175,15 +175,21 @@ class SearchUser(APIView):
def format_searched_user_result(request, users, size):
results = []
+ if ENABLE_SHOW_LOGIN_ID_WHEN_SEARCH_USER:
+ profile_queryset = Profile.objects.filter(user__in=users)
+ profile_dict = { p.user: p.login_id for p in profile_queryset if p.login_id }
for email in users:
url, is_default, date_uploaded = api_avatar_url(email, size)
- results.append({
+ user_info = {
"email": email,
"avatar_url": url,
"name": email2nickname(email),
"contact_email": email2contact_email(email),
- })
+ }
+ if ENABLE_SHOW_LOGIN_ID_WHEN_SEARCH_USER:
+ user_info['login_id'] = profile_dict.get(email, '')
+ results.append(user_info)
return results
diff --git a/seahub/api2/endpoints/share_link_zip_task.py b/seahub/api2/endpoints/share_link_zip_task.py
index e6410f3684..e20dbb8c24 100644
--- a/seahub/api2/endpoints/share_link_zip_task.py
+++ b/seahub/api2/endpoints/share_link_zip_task.py
@@ -10,7 +10,7 @@ from rest_framework import status
from django.conf import settings
-from seahub.api2.throttling import UserRateThrottle
+from seahub.api2.throttling import ShareLinkZipTaskThrottle
from seahub.api2.utils import api_error
from seahub.views.file import send_file_access_msg
@@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
class ShareLinkZipTaskView(APIView):
- throttle_classes = (UserRateThrottle,)
+ throttle_classes = (ShareLinkZipTaskThrottle, )
def get(self, request, format=None):
diff --git a/seahub/api2/endpoints/user.py b/seahub/api2/endpoints/user.py
index df4bd98910..eb49ca4bb6 100644
--- a/seahub/api2/endpoints/user.py
+++ b/seahub/api2/endpoints/user.py
@@ -23,7 +23,8 @@ from seahub.api2.utils import api_error
from seahub.base.templatetags.seahub_tags import email2nickname, \
email2contact_email
from seahub.profile.models import Profile, DetailedProfile
-from seahub.settings import ENABLE_UPDATE_USER_INFO, ENABLE_USER_SET_CONTACT_EMAIL, ENABLE_CONVERT_TO_TEAM_ACCOUNT
+from seahub.settings import ENABLE_UPDATE_USER_INFO, ENABLE_USER_SET_CONTACT_EMAIL, ENABLE_CONVERT_TO_TEAM_ACCOUNT, \
+ ENABLE_USER_SET_NAME
import seaserv
from seaserv import ccnet_api, seafile_api
@@ -91,6 +92,9 @@ class User(APIView):
# argument check for name
name = request.data.get("name", None)
if name:
+ if not ENABLE_USER_SET_NAME:
+ error_msg = _('Feature disabled.')
+ return api_error(status.HTTP_403_FORBIDDEN, error_msg)
name = name.strip()
if len(name) > 64:
error_msg = _('Name is too long (maximum is 64 characters)')
diff --git a/seahub/api2/serializers.py b/seahub/api2/serializers.py
index 8e95f2853e..a729b0e7d2 100644
--- a/seahub/api2/serializers.py
+++ b/seahub/api2/serializers.py
@@ -7,12 +7,15 @@ from seaserv import ccnet_api
from seahub.auth import authenticate
from seahub.api2.models import DESKTOP_PLATFORMS
from seahub.api2.utils import get_token_v1, get_token_v2
+from seahub.auth.utils import incr_login_failed_attempts
+from seahub.base.accounts import User
from seahub.profile.models import Profile
from seahub.two_factor.models import default_device
from seahub.two_factor.views.login import is_device_remembered
from seahub.utils.two_factor_auth import has_two_factor_auth, \
two_factor_auth_enabled, verify_two_factor_token
from seahub.settings import ENABLE_LDAP
+from constance import config
logger = logging.getLogger(__name__)
@@ -71,20 +74,29 @@ class AuthTokenSerializer(serializers.Serializer):
raise serializers.ValidationError('invalid params')
if login_id and password:
- user = authenticate(username=login_id, password=password)
- if user:
+ # First check the user is active or not
+ username = Profile.objects.convert_login_str_to_username(login_id)
+ if username is None:
+ username = login_id
+ try:
+ user = User.objects.get(username)
if not user.is_active:
raise serializers.ValidationError('User account is disabled.')
- else:
+ except User.DoesNotExist:
+ pass
+
+ # Second check the password correct or not
+ user = authenticate(username=login_id, password=password)
+ if not user:
"""try login id/contact email"""
# convert login id or contact email to username if any
- username = Profile.objects.convert_login_str_to_username(login_id)
user = authenticate(username=username, password=password)
# After local user authentication process is completed, authenticate LDAP user
if user is None and ENABLE_LDAP:
user = authenticate(ldap_user=username, password=password)
if user is None:
+ self._handle_failed_login(username)
raise serializers.ValidationError('Unable to login with provided credentials.')
elif not user.is_active:
raise serializers.ValidationError('User account is disabled.')
@@ -134,6 +146,23 @@ class AuthTokenSerializer(serializers.Serializer):
self.two_factor_auth_failed = True
msg = 'Two factor auth token is invalid.'
raise serializers.ValidationError(msg)
+
+ def _handle_failed_login(self, login_id):
+ failed_attempt = incr_login_failed_attempts(username=login_id)
+ if failed_attempt >= config.LOGIN_ATTEMPT_LIMIT:
+ if bool(config.FREEZE_USER_ON_LOGIN_FAILED) is True:
+ email = Profile.objects.get_username_by_login_id(login_id)
+ if email is None:
+ email = Profile.objects.get_username_by_contact_email(login_id)
+ if email is None:
+ email = login_id
+ try:
+ user = User.objects.get(email)
+ if user.is_active:
+ user.freeze_user(notify_admins=True, notify_org_admins=True)
+ except User.DoesNotExist:
+ pass
+ raise serializers.ValidationError('This account has been frozen due to too many failed login attempts.')
class AccountSerializer(serializers.Serializer):
diff --git a/seahub/api2/throttling.py b/seahub/api2/throttling.py
index a74b56a8a9..1b5e0fc8ea 100644
--- a/seahub/api2/throttling.py
+++ b/seahub/api2/throttling.py
@@ -7,6 +7,7 @@ from django.conf import settings
from django.core.cache import cache as default_cache
from django.core.exceptions import ImproperlyConfigured
from rest_framework.settings import api_settings
+from rest_framework.throttling import BaseThrottle
import time
from seahub.utils.ip import get_remote_ip
@@ -207,6 +208,22 @@ class UserRateThrottle(SimpleRateThrottle):
'ident': ident
}
+class ShareLinkZipTaskThrottle(SimpleRateThrottle):
+
+ scope = 'share_link_zip_task'
+
+ def get_cache_key(self, request, view):
+ if request.user.is_authenticated:
+ ident = request.user.id
+ else:
+ ident = self.get_ident(request)
+
+ return self.cache_format % {
+ 'scope': self.scope,
+ 'ident': ident
+ }
+
+
class ScopedRateThrottle(SimpleRateThrottle):
"""
diff --git a/seahub/api2/views.py b/seahub/api2/views.py
index 7f27ea2e19..b1344f2b4b 100644
--- a/seahub/api2/views.py
+++ b/seahub/api2/views.py
@@ -29,7 +29,7 @@ from django.http import HttpResponse
from django.template.defaultfilters import filesizeformat
from django.utils import timezone
from django.utils.translation import gettext as _
-
+from seahub.auth.utils import get_virtual_id_by_email
from .throttling import ScopedRateThrottle, AnonRateThrottle, UserRateThrottle
from .authentication import TokenAuthentication
from .serializers import AuthTokenSerializer
@@ -131,6 +131,11 @@ try:
from seahub.settings import ENABLE_OFFICE_WEB_APP
except ImportError:
ENABLE_OFFICE_WEB_APP = False
+
+try:
+ from seahub.settings import ORG_MEMBER_QUOTA_ENABLED
+except ImportError:
+ ORG_MEMBER_QUOTA_ENABLED = False
try:
from seahub.settings import OFFICE_WEB_APP_FILE_EXTENSION
@@ -1756,6 +1761,9 @@ class DownloadRepo(APIView):
forbidden_path = resp_json.get('forbidden_path', '')
if 'seadrive' in request.META.get('HTTP_USER_AGENT', '').lower():
+ # This is to help the desktop client to show error to the user.
+ # The actual permission check will be done at the file download time.
+
if not is_syncable and forbidden_path == '/':
error_msg = 'unsyncable share permission'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
@@ -5338,7 +5346,7 @@ class OrganizationView(APIView):
member_limit = request.POST.get('member_limit', ORG_MEMBER_QUOTA_DEFAULT)
if not org_name or not username or not password or \
- not prefix or not quota or not member_limit:
+ not prefix or not quota:
return api_error(status.HTTP_400_BAD_REQUEST, "Missing argument")
if not is_valid_username(username):
@@ -5350,8 +5358,9 @@ class OrganizationView(APIView):
logger.error(e)
return api_error(status.HTTP_400_BAD_REQUEST, "Quota is not valid")
+ vid = get_virtual_id_by_email(username)
try:
- User.objects.get(email = username)
+ User.objects.get(email = vid)
user_exist = True
except User.DoesNotExist:
user_exist = False
@@ -5367,15 +5376,16 @@ class OrganizationView(APIView):
return api_error(status.HTTP_400_BAD_REQUEST, "An organization with this prefix already exists")
try:
- User.objects.create_user(username, password, is_staff=False, is_active=True)
- create_org(org_name, prefix, username)
+ new_user = User.objects.create_user(username, password, is_staff=False, is_active=True)
+ create_org(org_name, prefix, new_user.username)
org = ccnet_threaded_rpc.get_org_by_url_prefix(prefix)
org_id = org.org_id
# set member limit
- from seahub.organizations.models import OrgMemberQuota
- OrgMemberQuota.objects.set_quota(org_id, member_limit)
+ if ORG_MEMBER_QUOTA_ENABLED:
+ from seahub.organizations.models import OrgMemberQuota
+ OrgMemberQuota.objects.set_quota(org_id, member_limit)
# set quota
quota = quota_mb * get_file_size_unit('MB')
diff --git a/seahub/auth/forms.py b/seahub/auth/forms.py
index b110846144..b8b5f2cd78 100644
--- a/seahub/auth/forms.py
+++ b/seahub/auth/forms.py
@@ -29,7 +29,7 @@ class AuthenticationForm(forms.Form):
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
- login = forms.CharField(label=_("Email or Username"), max_length=255)
+ login = forms.CharField(label=_("Email or Username") )
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
def __init__(self, request=None, *args, **kwargs):
@@ -41,6 +41,7 @@ class AuthenticationForm(forms.Form):
"""
self.request = request
self.user_cache = None
+ self.db_record = True
super(AuthenticationForm, self).__init__(*args, **kwargs)
def clean_login(self):
@@ -49,8 +50,25 @@ class AuthenticationForm(forms.Form):
def clean(self):
username = self.cleaned_data.get('login')
password = self.cleaned_data.get('password')
-
+
if username and password:
+ # First check the account length for validation.
+ if len(username) > 255:
+ self.errors['invalid_input'] = "The login name is too long."
+ self.db_record = False
+ raise forms.ValidationError("The login name is too long.")
+
+ # Check user account active or not
+ email = Profile.objects.convert_login_str_to_username(username)
+ try:
+ user = User.objects.get(email=email)
+ if not user.is_active:
+ self.errors['inactive'] = _("This account is inactive.")
+ raise forms.ValidationError(_("This account is inactive."))
+ except User.DoesNotExist:
+ pass
+
+ # Second check the password correct or not
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
"""then try login id/contact email/primary id"""
diff --git a/seahub/auth/views.py b/seahub/auth/views.py
index 415249579c..996f13bbfe 100644
--- a/seahub/auth/views.py
+++ b/seahub/auth/views.py
@@ -97,7 +97,6 @@ def login(request, template_name='registration/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm):
"""Displays the login form and handles the login action."""
-
redirect_to = request.GET.get(redirect_field_name, '')
if request.user.is_authenticated:
if redirect_to and url_has_allowed_host_and_scheme(redirect_to, allowed_hosts=request.get_host()):
@@ -130,7 +129,8 @@ def login(request, template_name='registration/login.html',
redirect_to, remember_me)
# form is invalid
- user_logged_in_failed.send(sender=None, request=request)
+ form.db_record and user_logged_in_failed.send(sender=None, request=request)
+
failed_attempt = incr_login_failed_attempts(username=login,
ip=ip)
diff --git a/seahub/organizations/api/admin/users.py b/seahub/organizations/api/admin/users.py
index 21cc561463..c038859431 100644
--- a/seahub/organizations/api/admin/users.py
+++ b/seahub/organizations/api/admin/users.py
@@ -839,7 +839,7 @@ class OrgAdminInviteUser(APIView):
new_user = User.objects.create_user(email, '!',
is_staff=False,
is_active=False)
- set_org_user(org_id, email)
+ set_org_user(org_id, new_user.username)
# send invitation link
i = Invitation.objects.add(inviter=username,
diff --git a/seahub/profile/templates/profile/set_profile_react.html b/seahub/profile/templates/profile/set_profile_react.html
index 922dcb15ee..b6019bfa8c 100644
--- a/seahub/profile/templates/profile/set_profile_react.html
+++ b/seahub/profile/templates/profile/set_profile_react.html
@@ -19,6 +19,7 @@ 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 %},
+ enableUserSetName: {% if ENABLE_USER_SET_NAME %} 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 %}
diff --git a/seahub/profile/views.py b/seahub/profile/views.py
index dcc626fcdb..aa349931a2 100644
--- a/seahub/profile/views.py
+++ b/seahub/profile/views.py
@@ -165,6 +165,7 @@ def edit_profile(request):
'enable_dingtalk': enable_dingtalk,
'social_connected_dingtalk': social_connected_dingtalk,
'ENABLE_USER_SET_CONTACT_EMAIL': settings.ENABLE_USER_SET_CONTACT_EMAIL,
+ 'ENABLE_USER_SET_NAME': settings.ENABLE_USER_SET_NAME,
'user_unusable_password': request.user.enc_password == UNUSABLE_PASSWORD,
'enable_adfs': enable_adfs,
'saml_connected': saml_connected,
diff --git a/seahub/settings.py b/seahub/settings.py
index 65b20a6a9d..1796c129ae 100644
--- a/seahub/settings.py
+++ b/seahub/settings.py
@@ -319,6 +319,7 @@ ENABLE_OAUTH = False
ENABLE_WATERMARK = False
ENABLE_SHOW_CONTACT_EMAIL_WHEN_SEARCH_USER = False
+ENABLE_SHOW_LOGIN_ID_WHEN_SEARCH_USER = False
# enable work weixin
ENABLE_WORK_WEIXIN = False
@@ -540,6 +541,7 @@ REST_FRAMEWORK = {
'ping': '3000/minute',
'anon': '60/minute',
'user': '3000/minute',
+ 'share_link_zip_task': '10/minute'
},
# https://github.com/tomchristie/django-rest-framework/issues/2891
'UNICODE_JSON': False,
@@ -758,6 +760,7 @@ WEBDAV_SECRET_MIN_LENGTH = 1
WEBDAV_SECRET_STRENGTH_LEVEL = 1
ENABLE_USER_SET_CONTACT_EMAIL = False
+ENABLE_USER_SET_NAME = True
# SSO to thirdparty website
ENABLE_SSO_TO_THIRDPART_WEBSITE = False
diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html
index eaab1fd686..91a7b52bc7 100644
--- a/seahub/templates/base_for_react.html
+++ b/seahub/templates/base_for_react.html
@@ -131,6 +131,7 @@
canInvitePeople: {% if enable_guest_invitation and user.permissions.can_invite_guest %} true {% else %} false {% endif %},
customNavItems: {% if custom_nav_items %} JSON.parse('{{ custom_nav_items | escapejs }}') {% else %} {{'[]'}} {% endif %},
enableShowContactEmailWhenSearchUser: {% if enable_show_contact_email_when_search_user %} true {% else %} false {% endif %},
+ enableShowLoginIDWhenSearchUser: {% if enable_show_login_id_when_search_user %} true {% else %} false {% endif %},
{% if max_upload_file_size > 0 %}
maxUploadFileSize: {{ max_upload_file_size }},
{% endif %}
diff --git a/seahub/templates/registration/login.html b/seahub/templates/registration/login.html
index a5ac09bbee..6b83948d5c 100644
--- a/seahub/templates/registration/login.html
+++ b/seahub/templates/registration/login.html
@@ -53,6 +53,8 @@ html, body, #wrapper { height:100%; }
{{ form.errors.freeze_account }}
{% elif form.errors.inactive %}
{{ form.errors.inactive }}
+ {% elif form.errors.invalid_input %}
+
{{ form.errors.invalid_input }}
{% elif form.errors.not_found %}
{{ form.errors.not_found }}
{% elif form.errors.disable_pwd_login %}
diff --git a/seahub/test_settings.py b/seahub/test_settings.py
index c37bf24deb..3eee9c7699 100644
--- a/seahub/test_settings.py
+++ b/seahub/test_settings.py
@@ -13,6 +13,7 @@ REST_FRAMEWORK = {
'ping': '90000/minute',
'anon': '90000/minute',
'user': '90000/minute',
+ 'share_link_zip_task': '90000/minute'
},
}
diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py
index e4e5f4ca72..68e299dede 100644
--- a/seahub/views/__init__.py
+++ b/seahub/views/__init__.py
@@ -1134,6 +1134,7 @@ def react_fake_view(request, **kwargs):
'file_audit_enabled': FILE_AUDIT_ENABLED,
'custom_nav_items': json.dumps(CUSTOM_NAV_ITEMS),
'enable_show_contact_email_when_search_user': settings.ENABLE_SHOW_CONTACT_EMAIL_WHEN_SEARCH_USER,
+ 'enable_show_login_id_when_search_user': settings.ENABLE_SHOW_LOGIN_ID_WHEN_SEARCH_USER,
'additional_share_dialog_note': ADDITIONAL_SHARE_DIALOG_NOTE,
'additional_app_bottom_links': ADDITIONAL_APP_BOTTOM_LINKS,
'additional_about_dialog_links': ADDITIONAL_ABOUT_DIALOG_LINKS,
diff --git a/tests/api/test_serializers.py b/tests/api/test_serializers.py
index 57d9c87900..88bfaa7c15 100644
--- a/tests/api/test_serializers.py
+++ b/tests/api/test_serializers.py
@@ -90,7 +90,8 @@ class AuthTokenSerializerTest(BaseTestCase):
}
s = AuthTokenSerializer(data=d, context={'request': self.fake_request})
- self.assertFailed(s)
+ assert s.is_valid() is False
+ assert 'User account is disabled.' in s.errors['non_field_errors']
def test_login_failed(self):
d = {
diff --git a/tests/seahub/auth/forms/test_authentication.py b/tests/seahub/auth/forms/test_authentication.py
index d320c78bbb..c829b4be9e 100644
--- a/tests/seahub/auth/forms/test_authentication.py
+++ b/tests/seahub/auth/forms/test_authentication.py
@@ -48,7 +48,8 @@ class AuthenticationFormTest(BaseTestCase):
}
form = AuthenticationForm(None, data)
- self.assertFailed(form)
+ assert form.is_valid() is False
+ assert 'This account is inactive.' in form.non_field_errors()
def test_login_success(self):
data = {