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 = {