text: # Copyright (c) 2012-2016 Seafile Ltd.
  import datetime
  import logging
  from rest_framework import status
  from rest_framework.authentication import BaseAuthentication
  from rest_framework.exceptions import APIException
  
  import seaserv
  from seahub.base.accounts import User
  from seahub.api2.models import Token, TokenV2
  from seahub.api2.utils import get_client_ip
  from seahub.utils import within_time_range
  from seahub.utils.user_permissions import populate_user_permissions
  try:
      from seahub.settings import MULTI_TENANCY
  except ImportError:
      MULTI_TENANCY = False
  
  logger = logging.getLogger(__name__)
  
  
  HEADER_CLIENT_VERSION = 'HTTP_X_SEAFILE_CLIENT_VERSION'
  HEADER_PLATFORM_VERSION = 'HTTP_X_SEAFILE_PLATFORM_VERSION'
  
  class AuthenticationFailed(APIException):
      status_code = status.HTTP_401_UNAUTHORIZED
      default_detail = 'Incorrect authentication credentials.'
  
      def __init__(self, detail=None):
          self.detail = detail or self.default_detail
  
  class DeviceRemoteWipedException(AuthenticationFailed):
      pass
  
  class TokenAuthentication(BaseAuthentication):
      """
      Simple token based authentication.
  
      Clients should authenticate by passing the token key in the "Authorization"
      HTTP header, prepended with the string "Token ".  For example:
  
          Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
  
      A custom token model may be used, but must have the following properties.
  
      * key -- The string identifying the token
      * user -- The user to which the token belongs
      """
  
      def authenticate(self, request):
          auth = request.META.get('HTTP_AUTHORIZATION', '').split()
          if not auth or auth[0].lower() != 'token':
              return None
  
          if len(auth) == 1:
              msg = 'Invalid token header. No credentials provided.'
              raise AuthenticationFailed(msg)
          elif len(auth) > 2:
              msg = 'Invalid token header. Token string should not contain spaces.'
              raise AuthenticationFailed(msg)
  
          key = auth[1]
          ret = self.authenticate_v2(request, key)
          if ret:
              return ret
  
          return self.authenticate_v1(request, key)
  
      def authenticate_v1(self, request, key):
          try:
              token = Token.objects.get(key=key)
          except Token.DoesNotExist:
              raise AuthenticationFailed('Invalid token')
  
          try:
              user = User.objects.get(email=token.user)
          except User.DoesNotExist:
              raise AuthenticationFailed('User inactive or deleted')
  
          if MULTI_TENANCY:
              orgs = seaserv.get_orgs_by_user(token.user)
              if orgs:
                  user.org = orgs[0]
  
          populate_user_permissions(user)
  
          if user.is_active:
              return (user, token)
  
      def authenticate_v2(self, request, key):
          try:
              token = TokenV2.objects.get(key=key)
          except TokenV2.DoesNotExist:
              # Continue authentication in token v1
              return None
  
          if token.wiped_at:
              raise DeviceRemoteWipedException('Device set to be remote wiped')
  
          try:
              user = User.objects.get(email=token.user)
          except User.DoesNotExist:
              raise AuthenticationFailed('User inactive or deleted')
  
          if MULTI_TENANCY:
              orgs = seaserv.get_orgs_by_user(token.user)
              if orgs:
                  user.org = orgs[0]
  
          populate_user_permissions(user)
  
          if user.is_active:
              need_save = False
  
              # We update the device's last_login_ip, client_version, platform_version if changed
              ip = get_client_ip(request)
              if ip and ip != token.last_login_ip:
                  token.last_login_ip = ip
                  need_save = True
  
              client_version = request.META.get(HEADER_CLIENT_VERSION, '')
              if client_version and client_version != token.client_version:
                  token.client_version = client_version
                  need_save = True
  
              platform_version = request.META.get(HEADER_PLATFORM_VERSION, '')
              if platform_version and platform_version != token.platform_version:
                  token.platform_version = platform_version
                  need_save = True
  
              if not within_time_range(token.last_accessed, datetime.datetime.now(), 10 * 60):
                  # We only need 10min precision for the last_accessed field
                  need_save = True
  
              if need_save:
                  try:
                      token.save()
                  except:
                      logger.exception('error when save token v2:')
  
              return (user, token)
  