1# Copyright (c) 2012-2016 Seafile Ltd.
2import logging
3
4from rest_framework import serializers
5from seaserv import ccnet_api
6
7from seahub.auth import authenticate
8from seahub.api2.models import DESKTOP_PLATFORMS
9from seahub.api2.utils import get_token_v1, get_token_v2
10from seahub.profile.models import Profile
11from seahub.two_factor.models import default_device
12from seahub.two_factor.views.login import is_device_remembered
13from seahub.utils.two_factor_auth import has_two_factor_auth, \
14        two_factor_auth_enabled, verify_two_factor_token
15
16logger = logging.getLogger(__name__)
17
18def all_none(values):
19    for value in values:
20        if value is not None:
21            return False
22
23    return True
24
25def all_not_none(values):
26    for value in values:
27        if value is None:
28            return False
29
30    return True
31
32class AuthTokenSerializer(serializers.Serializer):
33    username = serializers.CharField()
34    password = serializers.CharField()
35
36    # There fields are used by TokenV2
37    platform = serializers.CharField(required=False)
38    device_id = serializers.CharField(required=False)
39    device_name = serializers.CharField(required=False)
40
41    # These fields may be needed in the future
42    client_version = serializers.CharField(required=False, default='')
43    platform_version = serializers.CharField(required=False, default='')
44
45    def __init__(self, *a, **kw):
46        super(AuthTokenSerializer, self).__init__(*a, **kw)
47        self.two_factor_auth_failed = False
48
49    def validate(self, attrs):
50        login_id = attrs.get('username')
51        password = attrs.get('password')
52
53        platform = attrs.get('platform', None)
54        device_id = attrs.get('device_id', None)
55        device_name = attrs.get('device_name', None)
56        client_version = attrs.get('client_version', None)
57        platform_version = attrs.get('platform_version', None)
58
59        v2_fields = (platform, device_id, device_name)
60
61        # Decide the version of token we need
62        if all_none(v2_fields):
63            v2 = False
64        elif all_not_none(v2_fields):
65            v2 = True
66        else:
67            raise serializers.ValidationError('invalid params')
68
69        if login_id and password:
70            user = authenticate(username=login_id, password=password)
71            if user:
72                if not user.is_active:
73                    raise serializers.ValidationError('User account is disabled.')
74            else:
75                """try login id/contact email/primary id"""
76                # convert login id or contact email to username if any
77                username = Profile.objects.convert_login_str_to_username(login_id)
78                # convert username to primary id if any
79                p_id = ccnet_api.get_primary_id(username)
80                if p_id is not None:
81                    username = p_id
82
83                user = authenticate(username=username, password=password)
84                if user is None:
85                    raise serializers.ValidationError('Unable to login with provided credentials.')
86        else:
87            raise serializers.ValidationError('Must include "username" and "password"')
88
89        self._two_factor_auth(self.context['request'], user)
90
91        # Now user is authenticated
92        if v2:
93            if platform in DESKTOP_PLATFORMS:
94                if not user.permissions.can_connect_with_desktop_clients():
95                    raise serializers.ValidationError('Not allowed to connect to desktop client.')
96            elif platform == 'android':
97                if not user.permissions.can_connect_with_android_clients():
98                    raise serializers.ValidationError('Not allowed to connect to android client.')
99            elif platform == 'ios':
100                if not user.permissions.can_connect_with_ios_clients():
101                    raise serializers.ValidationError('Not allowed to connect to ios client.')
102            else:
103                logger.info('%s: unrecognized device' % login_id)
104
105            token = get_token_v2(self.context['request'], user.username, platform,
106                    device_id, device_name, client_version, platform_version)
107        else:
108            token = get_token_v1(user.username)
109
110        return token.key
111
112    def _two_factor_auth(self, request, user):
113        if not has_two_factor_auth() or not two_factor_auth_enabled(user):
114            return
115
116        if is_device_remembered(request.META.get('HTTP_X_SEAFILE_S2FA', ''),
117                                user):
118            return
119
120        token = request.META.get('HTTP_X_SEAFILE_OTP', '')
121        if not token:
122            # Generate challenge(send sms/call/...) if token is not provided.
123            default_device(user).generate_challenge()
124
125            self.two_factor_auth_failed = True
126            msg = 'Two factor auth token is missing.'
127            raise serializers.ValidationError(msg)
128        if not verify_two_factor_token(user, token):
129            self.two_factor_auth_failed = True
130            msg = 'Two factor auth token is invalid.'
131            raise serializers.ValidationError(msg)
132
133
134class AccountSerializer(serializers.Serializer):
135    email = serializers.EmailField()
136    password = serializers.CharField()
137    is_staff = serializers.BooleanField(default=False)
138    is_active = serializers.BooleanField(default=True)
139