# Copyright (c) 2012-2016 Seafile Ltd.
import os
import logging
from types import FunctionType
from constance import config

from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView

from django.db.models import Q
from django.core.cache import cache
from django.utils.translation import ugettext as _
from django.utils.timezone import make_naive, is_aware

from seaserv import seafile_api, ccnet_api

from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error, to_python_boolean
from seahub.api2.models import TokenV2

import seahub.settings as settings
from seahub.settings import SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER, INIT_PASSWD, \
    SEND_EMAIL_ON_RESETTING_USER_PASSWD
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
from seahub.base.accounts import User
from seahub.base.models import UserLastLogin
from seahub.two_factor.models import default_device
from seahub.profile.models import Profile
from seahub.profile.settings import CONTACT_CACHE_TIMEOUT, CONTACT_CACHE_PREFIX, \
    NICKNAME_CACHE_PREFIX, NICKNAME_CACHE_TIMEOUT
from seahub.utils import is_valid_username2, is_org_context, \
        is_pro_version, normalize_cache_key, is_valid_email, \
        IS_EMAIL_CONFIGURED, send_html_email, get_site_name, \
        gen_shared_link, gen_shared_upload_link

from seahub.utils.file_size import get_file_size_unit, byte_to_kb
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
        datetime_to_isoformat_timestr
from seahub.utils.user_permissions import get_user_role
from seahub.utils.repo import normalize_repo_status_code
from seahub.constants import DEFAULT_ADMIN
from seahub.role_permissions.models import AdminRole
from seahub.role_permissions.utils import get_available_roles
from seahub.utils.licenseparse import user_number_over_limit
from seahub.institutions.models import Institution
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.admin_log.signals import admin_operation
from seahub.admin_log.models import USER_DELETE, USER_ADD
from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner
from seahub.group.utils import group_id_to_name
from seahub.institutions.models import InstitutionAdmin

from seahub.options.models import UserOptions
from seahub.share.models import FileShare, UploadLinkShare

logger = logging.getLogger(__name__)
json_content_type = 'application/json; charset=utf-8'


def get_user_last_access_time(email, last_login_time):

    device_last_access = ''
    devices = TokenV2.objects.filter(user=email).order_by('-last_accessed')
    if devices:
        device_last_access = devices[0].last_accessed

    # before make_naive
    # 2021-04-09 05:32:30+00:00
    # tzinfo: UTC

    # after make_naive
    # 2021-04-09 13:32:30
    # tzinfo: None
    last_access_time_list = []
    if last_login_time:
        if is_aware(last_login_time):
            last_login_time = make_naive(last_login_time)
        last_access_time_list.append(last_login_time)

    if device_last_access:
        if is_aware(device_last_access):
            device_last_access = make_naive(device_last_access)
        last_access_time_list.append(device_last_access)

    if not last_access_time_list:
        return ''
    else:
        return datetime_to_isoformat_timestr(sorted(last_access_time_list)[-1])


def get_user_upload_link_info(uls):
    data = {}

    repo_id = uls.repo_id
    try:
        repo = seafile_api.get_repo(repo_id)
    except Exception as e:
        logger.error(e)
        repo = None

    path = uls.path
    if path:
        obj_name = '/' if path == '/' else os.path.basename(path.rstrip('/'))
    else:
        obj_name = ''

    data['repo_name'] = repo.repo_name if repo else ''
    data['path'] = path
    data['token'] = uls.token
    data['link'] = gen_shared_upload_link(uls.token)
    data['obj_name'] = obj_name
    data['view_cnt'] = uls.view_cnt

    return data


def get_user_share_link_info(fileshare):
    data = {}

    repo_id = fileshare.repo_id
    try:
        repo = seafile_api.get_repo(repo_id)
    except Exception as e:
        logger.error(e)
        repo = None

    path = fileshare.path
    if path:
        obj_name = '/' if path == '/' else os.path.basename(path.rstrip('/'))
    else:
        obj_name = ''

    data['repo_name'] = repo.repo_name if repo else ''
    data['token'] = fileshare.token
    data['link'] = gen_shared_link(fileshare.token, fileshare.s_type)

    data['path'] = path
    data['obj_name'] = obj_name
    data['is_dir'] = True if fileshare.s_type == 'd' else False

    data['view_cnt'] = fileshare.view_cnt

    if fileshare.s_type == 'f':
        obj_id = seafile_api.get_file_id_by_path(repo_id, path)
        data['size'] = seafile_api.get_file_size(repo.store_id,
                repo.version, obj_id)
    else:
        data['size'] = ''

    return data


def create_user_info(request, email, role, nickname, contact_email, quota_total_mb):
    # update additional user info

    if is_pro_version() and role:
        User.objects.update_role(email, role)

    if nickname is not None:
        Profile.objects.add_or_update(email, nickname)
        key = normalize_cache_key(nickname, NICKNAME_CACHE_PREFIX)
        cache.set(key, nickname, NICKNAME_CACHE_TIMEOUT)

    if contact_email is not None:
        Profile.objects.add_or_update(email, contact_email=contact_email)
        key = normalize_cache_key(email, CONTACT_CACHE_PREFIX)
        cache.set(key, contact_email, CONTACT_CACHE_TIMEOUT)

    if quota_total_mb:
        quota_total = int(quota_total_mb) * get_file_size_unit('MB')
        if is_org_context(request):
            org_id = request.user.org.org_id
            seafile_api.set_org_user_quota(org_id, email, quota_total)
        else:
            seafile_api.set_user_quota(email, quota_total)


def update_user_info(request, user, password, is_active, is_staff, role,
                     nickname, login_id, contact_email, reference_id,
                     quota_total_mb, institution_name,
                     upload_rate_limit, download_rate_limit):

    # update basic user info
    if is_active is not None:
        user.is_active = is_active

    if password:
        user.set_password(password)

    if is_staff is not None:
        user.is_staff = is_staff

    # update user
    user.save()

    email = user.username

    # update additional user info
    if is_pro_version() and role:
        User.objects.update_role(email, role)

    if nickname is not None:
        Profile.objects.add_or_update(email, nickname)
        key = normalize_cache_key(nickname, NICKNAME_CACHE_PREFIX)
        cache.set(key, nickname, NICKNAME_CACHE_TIMEOUT)

    if login_id is not None:
        Profile.objects.add_or_update(email, login_id=login_id)

    if contact_email is not None:
        Profile.objects.add_or_update(email, contact_email=contact_email)
        key = normalize_cache_key(email, CONTACT_CACHE_PREFIX)
        cache.set(key, contact_email, CONTACT_CACHE_TIMEOUT)

    if reference_id is not None:
        if reference_id.strip():
            ccnet_api.set_reference_id(email, reference_id.strip())
        else:
            # remove reference id
            ccnet_api.set_reference_id(email, None)

    if institution_name is not None:
        Profile.objects.add_or_update(email, institution=institution_name)
        if institution_name == '':
            InstitutionAdmin.objects.filter(user=email).delete()

    if quota_total_mb is not None:
        quota_total = int(quota_total_mb) * get_file_size_unit('MB')
        orgs = ccnet_api.get_orgs_by_user(email)
        try:
            if orgs:
                org_id = orgs[0].org_id
                seafile_api.set_org_user_quota(org_id, email, quota_total)
            else:
                seafile_api.set_user_quota(email, quota_total)
        except Exception as e:
            logger.error(e)
            seafile_api.set_user_quota(email, -1)

    if is_pro_version() and upload_rate_limit is not None:
        seafile_api.set_user_upload_rate_limit(email, upload_rate_limit * 1000)

    if is_pro_version() and download_rate_limit is not None:
        seafile_api.set_user_download_rate_limit(email, download_rate_limit * 1000)


def get_user_info(email):

    user = User.objects.get(email=email)
    profile = Profile.objects.get_profile_by_user(email)

    info = {}
    info['email'] = email
    info['name'] = email2nickname(email)
    info['contact_email'] = profile.contact_email if profile and profile.contact_email else ''
    info['login_id'] = profile.login_id if profile and profile.login_id else ''

    info['is_staff'] = user.is_staff
    info['is_active'] = user.is_active
    info['reference_id'] = user.reference_id if user.reference_id else ''

    orgs = ccnet_api.get_orgs_by_user(email)
    try:
        if orgs:
            org_id = orgs[0].org_id
            info['org_id'] = org_id
            info['org_name'] = orgs[0].org_name
            info['quota_usage'] = seafile_api.get_org_user_quota_usage(org_id, user.email)
            info['quota_total'] = seafile_api.get_org_user_quota(org_id, user.email)
        else:
            info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
            info['quota_total'] = seafile_api.get_user_quota(user.email)
    except Exception as e:
        logger.error(e)
        info['quota_usage'] = -1
        info['quota_total'] = -1

    info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)

    info['has_default_device'] = True if default_device(user) else False
    info['is_force_2fa'] = UserOptions.objects.is_force_2fa(email)

    if getattr(settings, 'MULTI_INSTITUTION', False):
        info['institution'] = profile.institution if profile else ''

    info['role'] = get_user_role(user)

    return info


class AdminAdminUsers(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def get(self, request):
        """List all admins from database and ldap imported
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        try:
            admin_users = ccnet_api.get_superusers()
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        admin_users_info = []
        for user in admin_users:
            user_info = {}
            profile = Profile.objects.get_profile_by_user(user.email)
            user_info['email'] = user.email
            user_info['name'] = email2nickname(user.email)
            user_info['contact_email'] = email2contact_email(user.email)
            user_info['login_id'] = profile.login_id if profile and profile.login_id else ''

            user_info['is_staff'] = user.is_staff
            user_info['is_active'] = user.is_active

            orgs = ccnet_api.get_orgs_by_user(user.email)
            try:
                if orgs:
                    org_id = orgs[0].org_id
                    user_info['org_id'] = org_id
                    user_info['org_name'] = orgs[0].org_name
                    user_info['quota_usage'] = seafile_api.get_org_user_quota_usage(org_id, user.email)
                    user_info['quota_total'] = seafile_api.get_org_user_quota(org_id, user.email)
                else:
                    user_info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
                    user_info['quota_total'] = seafile_api.get_user_quota(user.email)
            except Exception as e:
                logger.error(e)
                user_info['quota_usage'] = -1
                user_info['quota_total'] = -1

            user_info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)

            last_login_obj = UserLastLogin.objects.get_by_username(user.email)
            if last_login_obj:
                user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
                user_info['last_access_time'] = get_user_last_access_time(user.email,
                                                                          last_login_obj.last_login)
            else:
                user_info['last_login'] = ''
                user_info['last_access_time'] = get_user_last_access_time(user.email, '')

            try:
                admin_role = AdminRole.objects.get_admin_role(user.email)
                user_info['admin_role'] = admin_role.role
            except AdminRole.DoesNotExist:
                user_info['admin_role'] = DEFAULT_ADMIN
            admin_users_info.append(user_info)

        result = {
            'admin_user_list': admin_users_info,
        }
        return Response(result)


class AdminUsers(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def get_info_of_users_order_by_quota_usage(self, source, direction,
            page, per_page):

        # get user's quota usage info
        user_usage_dict = {}
        users_with_usage = seafile_api.list_user_quota_usage()
        for user in users_with_usage:
            email = user.user
            if email not in user_usage_dict:
                user_usage_dict[email] = user.usage

        # get all users and map quota usage to user
        if source == 'db':
            users = ccnet_api.get_emailusers('DB', -1, -1)
        else:
            users = ccnet_api.get_emailusers('LDAPImport', -1, -1)

        for user in users:
            email = user.email
            user.quota_usage = user_usage_dict.get(email, -1)

        # sort
        users.sort(key=lambda item: item.quota_usage,
                reverse = direction == 'desc')

        data = []
        MULTI_INSTITUTION = getattr(settings, 'MULTI_INSTITUTION', False)
        for user in users[(page-1)*per_page: page*per_page]:

            info = {}
            info['email'] = user.email
            info['name'] = email2nickname(user.email)
            info['contact_email'] = email2contact_email(user.email)

            profile = Profile.objects.get_profile_by_user(user.email)
            info['login_id'] = profile.login_id if profile and profile.login_id else ''

            info['is_staff'] = user.is_staff
            info['is_active'] = user.is_active
            info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)

            info['quota_usage'] = user.quota_usage
            info['quota_total'] = seafile_api.get_user_quota(user.email)

            last_login_obj = UserLastLogin.objects.get_by_username(user.email)
            if last_login_obj:
                info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
                info['last_access_time'] = get_user_last_access_time(user.email,
                                                                     last_login_obj.last_login)
            else:
                info['last_login'] = ''
                info['last_access_time'] = get_user_last_access_time(user.email, '')

            info['role'] = get_user_role(user)

            if MULTI_INSTITUTION:
                info['institution'] = profile.institution if profile else ''

            data.append(info)

        return data

    def get(self, request):
        """List all users in DB or LDAPImport

        Permission checking:
        1. only admin can perform this action.
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        # parameter check
        try:
            page = int(request.GET.get('page', '1'))
            per_page = int(request.GET.get('per_page', '25'))
        except ValueError:
            page = 1
            per_page = 25

        start = (page - 1) * per_page

        source = request.GET.get('source', 'DB').lower().strip()
        if source not in ['db', 'ldapimport']:
            # source: 'DB' or 'LDAPImport', default is 'DB'
            error_msg = 'source %s invalid.' % source
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        order_by = request.GET.get('order_by', '').lower().strip()
        if order_by:
            if order_by not in ('quota_usage'):
                error_msg = 'order_by invalid.'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            direction = request.GET.get('direction', 'desc').lower().strip()
            if direction not in ('asc', 'desc'):
                error_msg = 'direction invalid.'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if source == 'db':

            total_count = ccnet_api.count_emailusers('DB') + \
                          ccnet_api.count_inactive_emailusers('DB')
            if order_by:

                if total_count > 500 and \
                        not getattr(settings, 'ALWAYS_SORT_USERS_BY_QUOTA_USAGE', False):
                    error_msg = _("There are more than 500 users, and sort is not offered.")
                    return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

                try:
                    data = self.get_info_of_users_order_by_quota_usage(source,
                                                                       direction,
                                                                       page,
                                                                       per_page)
                except Exception as e:
                    logger.error(e)
                    error_msg = 'Internal Server Error'
                    return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

                result = {'data': data, 'total_count': total_count}
                return Response(result)
            else:
                users = ccnet_api.get_emailusers('DB', start, per_page)

        elif source == 'ldapimport':

            # api param is 'LDAP', but actually get count of 'LDAPImport' users
            total_count = ccnet_api.count_emailusers('LDAP') + \
                          ccnet_api.count_inactive_emailusers('LDAP')
            if order_by:

                if total_count > 500 and \
                        not getattr(settings, 'ALWAYS_SORT_USERS_BY_QUOTA_USAGE', False):
                    error_msg = _("There are more than 500 users, and sort is not offered.")
                    return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

                try:
                    data = self.get_info_of_users_order_by_quota_usage(source,
                                                                       direction,
                                                                       page,
                                                                       per_page)
                except Exception as e:
                    logger.error(e)
                    error_msg = 'Internal Server Error'
                    return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

                result = {'data': data, 'total_count': total_count}
                return Response(result)
            else:
                users = ccnet_api.get_emailusers('LDAPImport', start, per_page)

        data = []
        for user in users:
            profile = Profile.objects.get_profile_by_user(user.email)

            info = {}
            info['email'] = user.email
            info['name'] = email2nickname(user.email)
            info['contact_email'] = email2contact_email(user.email)
            info['login_id'] = profile.login_id if profile and profile.login_id else ''

            info['is_staff'] = user.is_staff
            info['is_active'] = user.is_active

            orgs = ccnet_api.get_orgs_by_user(user.email)
            try:
                if orgs:
                    org_id = orgs[0].org_id
                    info['org_id'] = org_id
                    info['org_name'] = orgs[0].org_name
                    info['quota_usage'] = seafile_api.get_org_user_quota_usage(org_id, user.email)
                    info['quota_total'] = seafile_api.get_org_user_quota(org_id, user.email)
                else:
                    info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
                    info['quota_total'] = seafile_api.get_user_quota(user.email)
            except Exception as e:
                logger.error(e)
                info['quota_usage'] = -1
                info['quota_total'] = -1

            info['role'] = get_user_role(user)

            info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)

            last_login_obj = UserLastLogin.objects.get_by_username(user.email)
            if last_login_obj:
                info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
                info['last_access_time'] = get_user_last_access_time(user.email,
                                                                     last_login_obj.last_login)
            else:
                info['last_login'] = ''
                info['last_access_time'] = get_user_last_access_time(user.email, '')

            if getattr(settings, 'MULTI_INSTITUTION', False):
                info['institution'] = profile.institution if profile else ''

            data.append(info)

        result = {'data': data, 'total_count': total_count}
        return Response(result)

    def post(self, request):

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        if user_number_over_limit():
            error_msg = _("The number of users exceeds the limit.")
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        email = request.data.get('email', None)
        if not email or not is_valid_email(email):
            error_msg = 'email invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # basic user info check
        is_staff = request.data.get("is_staff", 'False')
        try:
            is_staff = to_python_boolean(is_staff)
        except ValueError:
            error_msg = 'is_staff invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        is_active = request.data.get("is_active", 'True')
        try:
            is_active = to_python_boolean(is_active)
        except ValueError:
            error_msg = 'is_active invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # additional user info check
        role = ''
        if is_pro_version():
            role = request.data.get("role", None)
        if role:
            available_roles = get_available_roles()
            if role not in available_roles:
                error_msg = 'role must be in %s.' % str(available_roles)
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        name = request.data.get("name", None)
        if name:
            if len(name) > 64:
                error_msg = 'Name is too long (maximum is 64 characters).'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if "/" in name:
                error_msg = "Name should not include '/'."
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        contact_email = request.data.get('contact_email', None)
        if contact_email and not is_valid_email(contact_email):
            error_msg = 'contact_email invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        quota_total_mb = request.data.get("quota_total", None)
        if quota_total_mb:
            try:
                quota_total_mb = int(quota_total_mb)
            except ValueError:
                error_msg = "Must be an integer that is greater than or equal to 0."
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if quota_total_mb < 0:
                error_msg = "Space quota is too low (minimum value is 0)."
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if is_org_context(request):
                org_id = request.user.org.org_id
                org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')

                if quota_total_mb > org_quota_mb:
                    error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
                    return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        try:
            User.objects.get(email=email)
            user_exist = True
        except User.DoesNotExist:
            user_exist = False

        if user_exist:
            error_msg = "User %s already exists." % email
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        password = request.data.get('password', None)
        if not password:
            error_msg = 'password required.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # create user
        try:
            user_obj = User.objects.create_user(email, password, is_staff, is_active)
            create_user_info(request, email=user_obj.username, role=role,
                             nickname=name, contact_email=contact_email,
                             quota_total_mb=quota_total_mb)
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        add_user_tip = _('Successfully added user %(user)s.') % {'user': email}
        if IS_EMAIL_CONFIGURED and SEND_EMAIL_ON_ADDING_SYSTEM_MEMBER:
            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,
                                [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))
                add_user_tip = _('Successfully added user %(user)s. But email notification can not be sent, because Email service is not properly configured.') % {'user': email}

        user_info = get_user_info(email)
        user_info['add_user_tip'] = add_user_tip

        # send admin operation log signal
        admin_op_detail = {
            "email": email,
        }
        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)


class AdminLDAPUsers(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def get(self, request):
        """List all users from LDAP server

        Permission checking:
        1. only admin can perform this action.
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        try:
            page = int(request.GET.get('page', '1'))
            per_page = int(request.GET.get('per_page', '25'))
        except ValueError:
            page = 1
            per_page = 25

        start = (page - 1) * per_page
        end = page * per_page + 1
        users = ccnet_api.get_emailusers('LDAP', start, end)

        if len(users) == end - start:
            users = users[:per_page]
            has_next_page = True
        else:
            has_next_page = False

        data = []
        for user in users:
            info = {}
            info['email'] = user.email
            info['quota_total'] = seafile_api.get_user_quota(user.email)
            info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
            info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)

            last_login_obj = UserLastLogin.objects.get_by_username(user.email)
            if last_login_obj:
                info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
                info['last_access_time'] = get_user_last_access_time(user.email,
                                                                     last_login_obj.last_login)
            else:
                info['last_login'] = ''
                info['last_access_time'] = get_user_last_access_time(user.email, '')

            data.append(info)

        result = {'ldap_user_list': data, 'has_next_page': has_next_page}
        return Response(result)


class AdminSearchUser(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def get(self, request):
        """Search user from DB, LDAPImport and Profile

        Permission checking:
        1. only admin can perform this action.
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        query_str = request.GET.get('query', '').lower()
        if not query_str:
            error_msg = 'query invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        users = []

        page = request.GET.get('page', '')
        per_page = request.GET.get('per_page', '')

        if not page or not per_page:

            # search user from ccnet db
            users += ccnet_api.search_emailusers('DB', query_str, 0, 10)

            # search user from ccnet ldapimport
            users += ccnet_api.search_emailusers('LDAP', query_str, 0, 10)

            ccnet_user_emails = [u.email for u in users]

            # get institution for user from ccnet
            if getattr(settings, 'MULTI_INSTITUTION', False):
                user_institution_dict = {}
                profiles = Profile.objects.filter(user__in=ccnet_user_emails)
                for profile in profiles:
                    email = profile.user
                    if email not in user_institution_dict:
                        user_institution_dict[email] = profile.institution

                for user in users:
                    user.institution = user_institution_dict.get(user.email, '')

            # search user from profile
            searched_profile = Profile.objects.filter((Q(nickname__icontains=query_str)) | \
                                                       Q(contact_email__icontains=query_str))[:10]

            for profile in searched_profile:
                email = profile.user
                institution = profile.institution

                # remove duplicate emails
                if email not in ccnet_user_emails:
                    try:
                        # get is_staff and is_active info
                        user = User.objects.get(email=email)
                        user.institution = institution
                        users.append(user)
                    except User.DoesNotExist:
                        continue

            page_info = {
                'has_next_page': '',
                'current_page': ''
            }

        else:

            try:
                page = int(page)
                per_page = int(per_page)
            except ValueError:
                page = 1
                per_page = 25

            ccnet_users = []
            ccnet_db_users = ccnet_api.search_emailusers('DB', query_str, 0, page * per_page)
            ccnet_ldap_import_users = []

            if len(ccnet_db_users) == page * per_page:

                # users from ccnet db is enough
                ccnet_users = ccnet_db_users[-per_page:]

            elif len(ccnet_db_users) < page * per_page:

                ccnet_ldap_import_users = ccnet_api.search_emailusers('LDAP',
                                                                      query_str,
                                                                      0,
                                                                      page*per_page - len(ccnet_db_users))

                if int(len(ccnet_db_users)/per_page) == page-1:
                    # need ccnet_db_users + ccnet_ldap_import_users
                    ccnet_users = ccnet_db_users[(page-1)*per_page-len(ccnet_db_users):] + ccnet_ldap_import_users

                if int(len(ccnet_db_users)/per_page) < page-1:
                    # users only from ccnet_ldap_import_users
                    ccnet_users = ccnet_ldap_import_users[-per_page:]

            # search user from profile
            profile_users = []
            all_ccnet_users = ccnet_db_users + ccnet_ldap_import_users
            all_profile_users = []

            if len(all_ccnet_users) == page * per_page:

                # users from ccnet is enough
                users = ccnet_users

            if len(all_ccnet_users) < page * per_page:
                all_profile_users = Profile.objects.filter((Q(nickname__icontains=query_str)) | \
                                                           Q(contact_email__icontains=query_str)) \
                                                          [0:page*per_page-len(all_ccnet_users)]

                if int(len(all_ccnet_users)/per_page) == page-1:
                    # need ccnet users + profile users
                    tmp_users = []
                    for profile_user in all_profile_users:
                        try:
                            user = User.objects.get(email=profile_user.user)
                            tmp_users.append(user)
                        except User.DoesNotExist:
                            continue

                    users = ccnet_users + tmp_users

                if int(len(all_ccnet_users)/per_page) < page-1:
                    # only need profile users
                    for profile_user in list(all_profile_users)[-per_page:]:
                        try:
                            user = User.objects.get(email=profile_user.user)
                            users.append(user)
                        except User.DoesNotExist:
                            continue

            if len(all_ccnet_users) + len(all_profile_users) >= page * per_page:
                has_next_page = True
            else:
                has_next_page = False

            page_info = {
                'has_next_page': has_next_page,
                'current_page': page
            }

            # get institution for user from ccnet
            if getattr(settings, 'MULTI_INSTITUTION', False):
                for user in users:
                    if not hasattr(user, 'institution'):
                        try:
                            profile = Profile.objects.get(user=user.email)
                            user.institution = profile.institution
                        except Exception as e:
                            logger.error(e)

        data = []
        has_appended = []

        for user in users:

            if user.email in has_appended:
                continue
            else:
                has_appended.append(user.email)

            info = {}
            info['email'] = user.email
            info['name'] = email2nickname(user.email)
            info['contact_email'] = email2contact_email(user.email)

            info['is_staff'] = user.is_staff
            info['is_active'] = user.is_active

            info['source'] = user.source.lower()

            orgs = ccnet_api.get_orgs_by_user(user.email)
            if orgs:
                org_id = orgs[0].org_id
                info['org_id'] = org_id
                info['org_name'] = orgs[0].org_name
                info['quota_usage'] = seafile_api.get_org_user_quota_usage(org_id, user.email)
                info['quota_total'] = seafile_api.get_org_user_quota(org_id, user.email)
            else:
                info['quota_usage'] = seafile_api.get_user_self_usage(user.email)
                info['quota_total'] = seafile_api.get_user_quota(user.email)

            info['create_time'] = timestamp_to_isoformat_timestr(user.ctime)

            last_login_obj = UserLastLogin.objects.get_by_username(user.email)
            if last_login_obj:
                info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
                info['last_access_time'] = get_user_last_access_time(user.email,
                                                                     last_login_obj.last_login)
            else:
                info['last_login'] = ''
                info['last_access_time'] = get_user_last_access_time(user.email, '')

            info['role'] = get_user_role(user)

            if getattr(settings, 'MULTI_INSTITUTION', False):
                info['institution'] = user.institution

            data.append(info)

        result = {
            'user_list': data,
            'page_info': page_info,
        }
        return Response(result)


class AdminUser(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def get(self, request, email):

        if not (request.user.admin_permissions.can_manage_user() or \
            request.user.admin_permissions.can_update_user()):
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        avatar_size = request.data.get('avatar_size', 64)
        try:
            avatar_size = int(avatar_size)
        except Exception as e:
            logger.error(e)
            error_msg = 'avatar_size invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        try:
            User.objects.get(email=email)
        except User.DoesNotExist:
            error_msg = 'User %s not found.' % email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        user_info = get_user_info(email)
        user_info['avatar_url'], _, _ = api_avatar_url(email, avatar_size)
        if is_pro_version():
            user_info['upload_rate_limit'] = byte_to_kb(seafile_api.get_user_upload_rate_limit(email))
            user_info['download_rate_limit'] = byte_to_kb(seafile_api.get_user_download_rate_limit(email))

        last_login_obj = UserLastLogin.objects.get_by_username(email)
        if last_login_obj:
            user_info['last_login'] = datetime_to_isoformat_timestr(last_login_obj.last_login)
            user_info['last_access_time'] = get_user_last_access_time(email,
                                                                      last_login_obj.last_login)
        else:
            user_info['last_login'] = ''
            user_info['last_access_time'] = get_user_last_access_time(email, '')

        return Response(user_info)

    def put(self, request, email):

        if not (request.user.admin_permissions.can_manage_user() or \
            request.user.admin_permissions.can_update_user()):
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        # basic user info check
        is_staff = request.data.get("is_staff", None)
        if is_staff:
            try:
                is_staff = to_python_boolean(is_staff)
            except ValueError:
                error_msg = 'is_staff invalid.'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        is_active = request.data.get("is_active", None)
        if is_active:

            try:
                is_active = to_python_boolean(is_active)
            except ValueError:
                error_msg = 'is_active invalid.'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if is_pro_version() and is_active and user_number_over_limit(new_users=1):
                error_msg = _("The number of users exceeds the limit.")
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # additional user info check
        role = request.data.get("role", None)
        if role:
            available_roles = get_available_roles()
            if role not in available_roles:
                error_msg = 'role must be in %s.' % str(available_roles)
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        name = request.data.get("name", None)
        if name:
            if len(name) > 64:
                error_msg = 'Name is too long (maximum is 64 characters).'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if "/" in name:
                error_msg = "Name should not include '/'."
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # argument check for login_id
        login_id = request.data.get("login_id", None)
        if login_id is not None:
            login_id = login_id.strip()
            username_by_login_id = Profile.objects.get_username_by_login_id(login_id)
            if username_by_login_id is not None:
                return api_error(status.HTTP_400_BAD_REQUEST,
                                 _("Login id %s already exists." % login_id))

        contact_email = request.data.get("contact_email", None)
        if contact_email is not None and contact_email.strip() != '':
            if not is_valid_email(contact_email):
                error_msg = 'Contact email invalid.'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        password = request.data.get("password")

        reference_id = request.data.get("reference_id", None)
        if reference_id:
            if ' ' in reference_id:
                return api_error(status.HTTP_400_BAD_REQUEST, 'Reference ID can not contain spaces.')
            primary_id = ccnet_api.get_primary_id(reference_id)
            if primary_id:
                return api_error(status.HTTP_400_BAD_REQUEST, 'Reference ID %s already exists.' % reference_id)

        quota_total_mb = request.data.get("quota_total", None)
        if quota_total_mb:
            try:
                quota_total_mb = int(quota_total_mb)
            except ValueError:
                error_msg = "Must be an integer that is greater than or equal to 0."
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if quota_total_mb < 0:
                error_msg = "Space quota is too low (minimum value is 0)."
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if is_org_context(request):
                org_id = request.user.org.org_id
                org_quota_mb = seafile_api.get_org_quota(org_id) / get_file_size_unit('MB')

                if quota_total_mb > org_quota_mb:
                    error_msg = 'Failed to set quota: maximum quota is %d MB' % org_quota_mb
                    return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        institution = request.data.get("institution", None)
        if institution:
            try:
                Institution.objects.get(name=institution)
            except Institution.DoesNotExist:
                error_msg = 'Institution %s does not exist' % institution
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        upload_rate_limit = request.data.get("upload_rate_limit", None)
        if upload_rate_limit:

            if not is_pro_version():
                error_msg = 'Feature disabled.'
                return api_error(status.HTTP_403_FORBIDDEN, error_msg)

            try:
                upload_rate_limit = int(upload_rate_limit)
            except ValueError:
                error_msg = _('Must be an integer that is greater than or equal to 0.')
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if upload_rate_limit < 0:
                error_msg = _('Must be an integer that is greater than or equal to 0.')
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        download_rate_limit = request.data.get("download_rate_limit", None)
        if download_rate_limit:

            if not is_pro_version():
                error_msg = 'Feature disabled.'
                return api_error(status.HTTP_403_FORBIDDEN, error_msg)

            try:
                download_rate_limit = int(download_rate_limit)
            except ValueError:
                error_msg = _('Must be an integer that is greater than or equal to 0.')
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if download_rate_limit < 0:
                error_msg = _('Must be an integer that is greater than or equal to 0.')
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # query user info
        try:
            user_obj = User.objects.get(email=email)
        except User.DoesNotExist:
            error_msg = 'User %s not found.' % email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        try:
            update_user_info(request,
                             user=user_obj,
                             password=password,
                             is_active=is_active,
                             is_staff=is_staff,
                             role=role,
                             nickname=name,
                             login_id=login_id,
                             contact_email=contact_email,
                             reference_id=reference_id,
                             quota_total_mb=quota_total_mb,
                             institution_name=institution,
                             upload_rate_limit=upload_rate_limit,
                             download_rate_limit=download_rate_limit)
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        # update user
        try:
            user_obj.save()
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        update_status_tip = ''
        if is_active is not None:
            update_status_tip = _('Edit succeeded')
            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,
                                    [email2contact_email(user_obj.email)])
                    update_status_tip = _('Edit succeeded, an email has been sent.')
                except Exception as e:
                    logger.error(e)
                    update_status_tip = _('Edit succeeded, but failed to send email, please check your email configuration.')

        user_info = get_user_info(email)
        user_info['update_status_tip'] = update_status_tip
        if is_pro_version():
            user_info['upload_rate_limit'] = byte_to_kb(seafile_api.get_user_upload_rate_limit(email))
            user_info['download_rate_limit'] = byte_to_kb(seafile_api.get_user_download_rate_limit(email))

        return Response(user_info)

    def delete(self, request, email):

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        try:
            User.objects.get(email=email)
        except User.DoesNotExist:
            error_msg = 'User %s not found.' % email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        # delete user
        try:
            User.objects.get(email=email).delete()
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        # send admin operation log signal
        admin_op_detail = {
            "email": email,
        }
        admin_operation.send(sender=None, admin_name=request.user.username,
                             operation=USER_DELETE, detail=admin_op_detail)

        return Response({'success': True})


class AdminUserResetPassword(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def put(self, request, email):
        """Reset password for user

        Permission checking:
        1. only admin can perform this action.
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        if not is_valid_username2(email):
            error_msg = 'email invalid'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        try:
            user = User.objects.get(email=email)
        except User.DoesNotExist as e:
            logger.error(e)
            error_msg = 'email invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if isinstance(INIT_PASSWD, FunctionType):
            new_password = INIT_PASSWD()
        else:
            new_password = INIT_PASSWD
        user.set_password(new_password)
        user.save()

        if config.FORCE_PASSWORD_CHANGE:
            UserOptions.objects.set_force_passwd_change(user.username)

        if IS_EMAIL_CONFIGURED:
            if SEND_EMAIL_ON_RESETTING_USER_PASSWD:
                c = {'email': email, 'password': new_password}
                contact_email = Profile.objects.get_contact_email_by_user(email)
                try:
                    send_html_email(_(u'Password has been reset on %s') % get_site_name(),
                                    'sysadmin/user_reset_email.html', c, None, [contact_email])
                    reset_tip = _('Successfully reset password to %(passwd)s, an email has been sent to %(user)s.') % \
                        {'passwd': new_password, 'user': contact_email}
                except Exception as e:
                    logger.warning(e)
                    reset_tip = _('Successfully reset password to %(passwd)s, but failed to send email to %(user)s, please check your email configuration.') % \
                        {'passwd': new_password, 'user': email}
            else:
                reset_tip = _('Successfully reset password to %(passwd)s for user %(user)s.') % \
                    {'passwd': new_password, 'user': email}
        else:
            reset_tip = _('Successfully reset password to %(passwd)s for user %(user)s. But email notification can not be sent, because Email service is not properly configured.') % \
                {'passwd': new_password, 'user': email}

        return Response({'new_password': new_password, 'reset_tip': reset_tip})


class AdminUserGroups(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def get(self, request, email):
        """ return all groups user joined

        Permission checking:
        1. Admin user;
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        try:
            User.objects.get(email=email)
        except User.DoesNotExist as e:
            logger.error(e)
            error_msg = 'User %s not found.' % email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        groups_info = []
        try:
            groups = ccnet_api.get_groups(email)
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        # Use dict to reduce memcache fetch cost in large for-loop.
        nickname_dict = {}
        creator_name_set = set([g.creator_name for g in groups])
        for e in creator_name_set:
            if e not in nickname_dict:
                nickname_dict[e] = email2nickname(e)

        for group in groups:
            isoformat_timestr = timestamp_to_isoformat_timestr(group.timestamp)
            group_info = {
                "id": group.id,
                "name": group.group_name,
                "owner_email": group.creator_name,
                "owner_name": nickname_dict.get(group.creator_name, ''),
                "created_at": isoformat_timestr,
                "parent_group_id": group.parent_group_id if is_pro_version() else 0
            }
            groups_info.append(group_info)

            try:
                is_group_staff = ccnet_api.check_group_staff(group.id, email)
            except Exception as e:
                logger.error(e)
                error_msg = 'Internal Server Error'
                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

            if email == group.creator_name:
                group_info['role'] = 'Owner'
            elif is_group_staff:
                group_info['role'] = 'Admin'
            else:
                group_info['role'] = 'Member'
        return Response({'group_list': groups_info})


class AdminUserShareLinks(APIView):
    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser,)
    throttle_classes = (UserRateThrottle,)

    def get(self, request, email):
        """ Get all shared download links of a user.

        Permission checking:
        1. only admin can perform this action.
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        try:
            User.objects.get(email=email)
        except User.DoesNotExist as e:
            logger.error(e)
            error_msg = 'User %s not found.' % email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        share_links = FileShare.objects.filter(username=email)

        links_info = []
        for fs in share_links:
            link_info = get_user_share_link_info(fs)
            links_info.append(link_info)

        return Response({'share_link_list': links_info})


class AdminUserUploadLinks(APIView):
    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser,)
    throttle_classes = (UserRateThrottle,)

    def get(self, request, email):
        """ Get all shared upload links of a user.

        Permission checking:
        1. only admin can perform this action.
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        try:
            User.objects.get(email=email)
        except User.DoesNotExist as e:
            logger.error(e)
            error_msg = 'User %s not found.' % email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        upload_links = UploadLinkShare.objects.filter(username=email)

        links_info = []
        for fs in upload_links:
            link_info = get_user_upload_link_info(fs)
            links_info.append(link_info)

        return Response({'upload_link_list': links_info})


class AdminUserBeSharedRepos(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    throttle_classes = (UserRateThrottle,)
    permission_classes = (IsAdminUser,)

    def get(self, request, email):
        """ List 'all' libraries shared to a user

        Permission checking:
        1. only admin can perform this action.
        """

        if not request.user.admin_permissions.can_manage_user():
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        try:
            User.objects.get(email=email)
        except User.DoesNotExist as e:
            logger.error(e)
            error_msg = 'User %s not found.' % email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        try:
            beshared_repos = seafile_api.get_share_in_repo_list(email, -1, -1)
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        # Use dict to reduce memcache fetch cost in large for-loop.
        nickname_dict = {}
        owner_set = set([x.user for x in beshared_repos])
        for email in owner_set:
            if email not in nickname_dict:
                if '@seafile_group' in email:
                    group_id = get_group_id_by_repo_owner(email)
                    group_name = group_id_to_name(group_id)
                    nickname_dict[email] = group_name
                else:
                    nickname_dict[email] = email2nickname(email)

        repos_info = []
        for repo in beshared_repos:
            repo_info = {}
            repo_info['id'] = repo.repo_id
            repo_info['name'] = repo.repo_name
            repo_info['owner_email'] = repo.user
            repo_info['owner_name'] = nickname_dict.get(repo.user, '')
            repo_info['size'] = repo.size
            repo_info['encrypted'] = repo.encrypted
            repo_info['file_count'] = repo.file_count
            repo_info['status'] = normalize_repo_status_code(repo.status)
            repo_info['last_modify'] = timestamp_to_isoformat_timestr(repo.last_modify)

            repos_info.append(repo_info)

        return Response({'repo_list': repos_info})


class AdminUpdateUserCcnetEmail(APIView):

    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAdminUser, )
    throttle_classes = (UserRateThrottle, )

    def put(self, request):
        """update ccnet email

        Permission checking:
        1. only admin can perform this action.
        """

        # argument check
        old_ccnet_email = request.data.get("old_email", None)
        if not old_ccnet_email:
            error_msg = 'old_email invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        new_ccnet_email = request.data.get("new_email", None)
        if not new_ccnet_email:
            error_msg = 'new_email invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        new_ccnet_email = new_ccnet_email.strip()
        if not is_valid_email(new_ccnet_email):
            error_msg = 'new_email invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # resource check
        if not ccnet_api.get_emailuser(old_ccnet_email):
            error_msg = 'User %s not found.' % old_ccnet_email
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        if ccnet_api.get_emailuser(new_ccnet_email):
            error_msg = "User %s already exists." % new_ccnet_email
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # update
        try:
            ccnet_api.update_emailuser_id(old_ccnet_email, new_ccnet_email)
            logger.debug('the ccnet database was successfully updated')
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        try:
            from seahub.api2.models import Token
            token_list = Token.objects.filter(user=old_ccnet_email)
            for token in token_list:
                token.user = new_ccnet_email
                token.save()
            logger.debug('the api2_token table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        try:
            from seahub.api2.models import TokenV2
            tokenv2_list = TokenV2.objects.filter(user=old_ccnet_email)
            for tokenv2 in tokenv2_list:
                tokenv2.user = new_ccnet_email
                tokenv2.save()
            logger.debug('the api2_tokenv2 table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)
            error_msg = 'Internal Server Error'
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

        try:
            from seahub.admin_log.models import AdminLog
            adminlog_list = AdminLog.objects.filter(email=old_ccnet_email)
            for adminlog in adminlog_list:
                adminlog.email = new_ccnet_email
                adminlog.save()
            logger.debug('the admin_log_adminlog table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.avatar.models import Avatar
            avatar_list = Avatar.objects.filter(emailuser=old_ccnet_email)
            for avatar in avatar_list:
                avatar.emailuser = new_ccnet_email
                avatar.save()
            logger.debug('the avatar_avatar table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.base.models import ClientLoginToken
            clientlogintoken_list = ClientLoginToken.objects.filter(username=old_ccnet_email)
            for clientlogintoken in clientlogintoken_list:
                clientlogintoken.username = new_ccnet_email
                clientlogintoken.save()
            logger.debug('the base_clientlogintoken table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.base.models import DeviceToken
            devicetoken_list = DeviceToken.objects.filter(user=old_ccnet_email)
            for devicetoken in devicetoken_list:
                devicetoken.user = new_ccnet_email
                devicetoken.save()
            logger.debug('the base_devicetoken table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.base.models import FileComment
            filecomment_list = FileComment.objects.filter(author=old_ccnet_email)
            for filecomment in filecomment_list:
                filecomment.author = new_ccnet_email
                filecomment.save()
            logger.debug('the base_filecomment table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.base.models import UserLastLogin
            userlastlogin_list = UserLastLogin.objects.filter(username=old_ccnet_email)
            for userlastlogin in userlastlogin_list:
                userlastlogin.username = new_ccnet_email
                userlastlogin.save()
            logger.debug('the base_userlastlogin table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.base.models import UserStarredFiles
            userstarredfiles_list = UserStarredFiles.objects.filter(email=old_ccnet_email)
            for userstarredfiles in userstarredfiles_list:
                userstarredfiles.email = new_ccnet_email
                userstarredfiles.save()
            logger.debug('the base_userstarredfiles table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.drafts.models import Draft
            draft_list = Draft.objects.filter(username=old_ccnet_email)
            for draft in draft_list:
                draft.username = new_ccnet_email
                draft.save()
            logger.debug('the drafts_draft table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.drafts.models import DraftReviewer
            draftreviewer_list = DraftReviewer.objects.filter(reviewer=old_ccnet_email)
            for draftreviewer in draftreviewer_list:
                draftreviewer.reviewer = new_ccnet_email
                draftreviewer.save()
            logger.debug('the drafts_draftreviewer table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.file_participants.models import FileParticipant
            fileparticipant_list = FileParticipant.objects.filter(username=old_ccnet_email)
            for fileparticipant in fileparticipant_list:
                fileparticipant.username = new_ccnet_email
                fileparticipant.save()
            logger.debug('the file_participants_fileparticipant table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.institutions.models import InstitutionAdmin
            institutionadmin_list = InstitutionAdmin.objects.filter(user=old_ccnet_email)
            for institutionadmin in institutionadmin_list:
                institutionadmin.user = new_ccnet_email
                institutionadmin.save()
            logger.debug('the institutions_institutionadmin table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.invitations.models import Invitation
            invitation_list = Invitation.objects.filter(inviter=old_ccnet_email)
            for invitation in invitation_list:
                invitation.inviter = new_ccnet_email
                invitation.save()
            logger.debug('the invitations_invitation table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.notifications.models import UserNotification
            usernotification_list = UserNotification.objects.filter(to_user=old_ccnet_email)
            for usernotification in usernotification_list:
                usernotification.to_user = new_ccnet_email
                usernotification.save()
            logger.debug('the notifications_usernotification table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.options.models import UserOptions
            useroptions_list = UserOptions.objects.filter(email=old_ccnet_email)
            for useroptions in useroptions_list:
                useroptions.email = new_ccnet_email
                useroptions.save()
            logger.debug('the options_useroptions table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.profile.models import DetailedProfile
            detailedprofile_list = DetailedProfile.objects.filter(user=old_ccnet_email)
            for detailedprofile in detailedprofile_list:
                detailedprofile.user = new_ccnet_email
                detailedprofile.save()
            logger.debug('the profile_detailedprofile table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.profile.models import Profile
            profile_list = Profile.objects.filter(user=old_ccnet_email)
            for profile in profile_list:
                profile.user = new_ccnet_email
                profile.save()
            logger.debug('the profile_profile table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.role_permissions.models import AdminRole
            adminrole_list = AdminRole.objects.filter(email=old_ccnet_email)
            for adminrole in adminrole_list:
                adminrole.email = new_ccnet_email
                adminrole.save()
            logger.debug('the role_permissions_adminrole table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.share.models import AnonymousShare
            anonymousshare_list = AnonymousShare.objects.filter(repo_owner=old_ccnet_email)
            for anonymousshare in anonymousshare_list:
                anonymousshare.repo_owner = new_ccnet_email
                anonymousshare.save()
            logger.debug('the share_anonymousshare table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.share.models import FileShare
            fileshare_list = FileShare.objects.filter(username=old_ccnet_email)
            for fileshare in fileshare_list:
                fileshare.username = new_ccnet_email
                fileshare.save()
            logger.debug('the share_fileshare table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.share.models import UploadLinkShare
            uploadlinkshare_list = UploadLinkShare.objects.filter(username=old_ccnet_email)
            for uploadlinkshare in uploadlinkshare_list:
                uploadlinkshare.username = new_ccnet_email
                uploadlinkshare.save()
            logger.debug('the share_uploadlinkshare table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.auth.models import SocialAuthUser
            socialauthuser_list = SocialAuthUser.objects.filter(username=old_ccnet_email)
            for socialauthuser in socialauthuser_list:
                socialauthuser.username = new_ccnet_email
                socialauthuser.save()
            logger.debug('the social_auth_usersocialauth table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.sysadmin_extra.models import UserLoginLog
            userlastlogin_list = UserLoginLog.objects.filter(username=old_ccnet_email)
            for userlastlogin in userlastlogin_list:
                userlastlogin.username = new_ccnet_email
                userlastlogin.save()
            logger.debug('the sysadmin_extra_userloginlog table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.tags.models import FileTag
            filetag_list = FileTag.objects.filter(username=old_ccnet_email)
            for filetag in filetag_list:
                filetag.username = new_ccnet_email
                filetag.save()
            logger.debug('the tags_filetag table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from termsandconditions.models import UserTermsAndConditions
            usertermsandconditions_list = UserTermsAndConditions.objects.filter(username=old_ccnet_email)
            for usertermsandconditions in usertermsandconditions_list:
                usertermsandconditions.username = new_ccnet_email
                usertermsandconditions.save()
            logger.debug('the termsandconditions_usertermsandconditions table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.wiki.models import Wiki
            wiki_list = Wiki.objects.filter(username=old_ccnet_email)
            for wiki in wiki_list:
                wiki.username = new_ccnet_email
                wiki.save()
            logger.debug('the wiki_wiki table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.ocm.models import OCMShare
            ocmshare_list = OCMShare.objects.filter(from_user=old_ccnet_email)
            for ocmshare in ocmshare_list:
                ocmshare.from_user = new_ccnet_email
                ocmshare.save()
            logger.debug('the ocm_share table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        try:
            from seahub.ocm.models import OCMShareReceived
            ocmsharereceived_list = OCMShareReceived.objects.filter(to_user=old_ccnet_email)
            for ocmsharereceived in ocmsharereceived_list:
                ocmsharereceived.to_user = new_ccnet_email
                ocmsharereceived.save()
            logger.debug('the ocm_share_received table in seahub database was successfully updated')
        except Exception as e:
            logger.error(e)

        return Response({'success': True})
