1# Copyright (c) 2012-2016 Seafile Ltd.
2# -*- coding: utf-8 -*-
3from datetime import datetime
4import logging
5
6from django.db import models
7
8from seahub.base.fields import LowerCaseCharField
9from seahub.utils import is_pro_version
10
11# Get an instance of a logger
12logger = logging.getLogger(__name__)
13
14KEY_SERVER_CRYPTO = "server_crypto"
15VAL_SERVER_CRYPTO_ENABLED = "1"
16VAL_SERVER_CRYPTO_DISABLED = "0"
17
18KEY_USER_GUIDE = "user_guide"
19VAL_USER_GUIDE_ON = "1"
20VAL_USER_GUIDE_OFF = "0"
21
22KEY_SUB_LIB = "sub_lib"
23VAL_SUB_LIB_ENABLED = "1"
24VAL_SUB_LIB_DISABLED = "0"
25
26KEY_FORCE_PASSWD_CHANGE = "force_passwd_change"
27VAL_FORCE_PASSWD_CHANGE = "1"
28
29KEY_FORCE_2FA = "force_2fa"
30VAL_FORCE_2FA = "1"
31
32KEY_USER_LOGGED_IN = "user_logged_in"
33VAL_USER_LOGGED_IN = "1"
34
35KEY_DEFAULT_REPO = "default_repo"
36KEY_WEBDAV_SECRET = "webdav_secret"
37KEY_FILE_UPDATES_EMAIL_INTERVAL = "file_updates_email_interval"
38KEY_FILE_UPDATES_LAST_EMAILED_TIME = "file_updates_last_emailed_time"
39KEY_COLLABORATE_EMAIL_INTERVAL = 'collaborate_email_interval'
40KEY_COLLABORATE_LAST_EMAILED_TIME = 'collaborate_last_emailed_time'
41
42DEFAULT_COLLABORATE_EMAIL_INTERVAL = 3600
43
44
45class CryptoOptionNotSetError(Exception):
46    pass
47
48class UserOptionsManager(models.Manager):
49    def set_user_option(self, username, k, v):
50        """
51
52        Arguments:
53        - `username`:
54        - `k`:
55        - `v`:
56        """
57        try:
58            user_option = super(UserOptionsManager, self).get(email=username,
59                                                              option_key=k)
60            user_option.option_val = v
61        except UserOptions.DoesNotExist:
62            user_option = self.model(email=username, option_key=k,
63                                     option_val=v)
64        user_option.save(using=self._db)
65
66        return user_option
67
68    def get_user_option(self, username, k):
69        user_options = super(UserOptionsManager, self).filter(
70            email=username, option_key=k)
71
72        if len(user_options) == 0:
73            return None
74        elif len(user_options) == 1:
75            return user_options[0].option_val
76        else:
77            for o in user_options[1: len(user_options)]:
78                o.delete()
79
80            return user_options[0].option_val
81
82    def unset_user_option(self, username, k):
83        """Remove user's option.
84        """
85        super(UserOptionsManager, self).filter(email=username, option_key=k).delete()
86
87    def enable_server_crypto(self, username):
88        """
89
90        Arguments:
91        - `username`:
92        """
93        return self.set_user_option(username, KEY_SERVER_CRYPTO,
94                                    VAL_SERVER_CRYPTO_ENABLED)
95
96    def disable_server_crypto(self, username):
97        """
98
99        Arguments:
100        - `username`:
101        """
102        return self.set_user_option(username, KEY_SERVER_CRYPTO,
103                                    VAL_SERVER_CRYPTO_DISABLED)
104
105    def is_server_crypto(self, username):
106        """Client crypto is deprecated, always return ``True``.
107        """
108        return True
109
110    def enable_user_guide(self, username):
111        """
112
113        Arguments:
114        - `self`:
115        - `username`:
116        """
117        return self.set_user_option(username, KEY_USER_GUIDE,
118                                    VAL_USER_GUIDE_ON)
119
120    def disable_user_guide(self, username):
121        """
122
123        Arguments:
124        - `self`:
125        - `username`:
126        """
127        return self.set_user_option(username, KEY_USER_GUIDE,
128                                    VAL_USER_GUIDE_OFF)
129
130    def is_user_guide_enabled(self, username):
131        """Return ``True`` if user need guide, otherwise ``False``.
132
133        Arguments:
134        - `self`:
135        - `username`:
136        """
137        rst = super(UserOptionsManager, self).filter(
138            email=username, option_key=KEY_USER_GUIDE)
139        rst_len = len(rst)
140        if rst_len <= 0:
141            # Assume ``user_guide`` is enabled if this optoin is not set.
142            return True
143        elif rst_len == 1:
144            return bool(int(rst[0].option_val))
145        else:
146            for i in range(rst_len - 1):
147                rst[i].delete()
148            return bool(int(rst[rst_len - 1].option_val))
149
150    def enable_sub_lib(self, username):
151        """
152
153        Arguments:
154        - `self`:
155        - `username`:
156        """
157        return self.set_user_option(username, KEY_SUB_LIB,
158                                    VAL_SUB_LIB_ENABLED)
159
160    def disable_sub_lib(self, username):
161        """
162
163        Arguments:
164        - `self`:
165        - `username`:
166        """
167        return self.set_user_option(username, KEY_SUB_LIB,
168                                    VAL_SUB_LIB_DISABLED)
169
170    def is_sub_lib_enabled(self, username):
171        """Return ``True`` if is not pro version AND sub lib enabled, otherwise ``False``.
172
173        Arguments:
174        - `self`:
175        - `username`:
176        """
177        if is_pro_version():
178            return False
179
180        try:
181            user_option = super(UserOptionsManager, self).get(
182                email=username, option_key=KEY_SUB_LIB)
183            return bool(int(user_option.option_val))
184        except UserOptions.DoesNotExist:
185            return False
186
187    def set_default_repo(self, username, repo_id):
188        """Set a user's default library.
189
190        Arguments:
191        - `self`:
192        - `username`:
193        - `repo_id`:
194        """
195        return self.set_user_option(username, KEY_DEFAULT_REPO, repo_id)
196
197    def get_default_repo(self, username):
198        """Get a user's default library.
199
200        Returns repo_id if default library is found, otherwise ``None``.
201
202        Arguments:
203        - `self`:
204        - `username`:
205        """
206        return self.get_user_option(username, KEY_DEFAULT_REPO)
207
208    def passwd_change_required(self, username):
209        """Check whether user need to change password.
210        """
211        try:
212            r = super(UserOptionsManager, self).get(
213                email=username, option_key=KEY_FORCE_PASSWD_CHANGE)
214            return r.option_val == VAL_FORCE_PASSWD_CHANGE
215        except UserOptions.DoesNotExist:
216            return False
217
218    def set_force_passwd_change(self, username):
219        return self.set_user_option(username, KEY_FORCE_PASSWD_CHANGE,
220                                    VAL_FORCE_PASSWD_CHANGE)
221
222    def unset_force_passwd_change(self, username):
223        return self.unset_user_option(username, KEY_FORCE_PASSWD_CHANGE)
224
225    def set_force_2fa(self, username):
226        return self.set_user_option(username, KEY_FORCE_2FA, VAL_FORCE_2FA)
227
228    def unset_force_2fa(self, username):
229        return self.unset_user_option(username, KEY_FORCE_2FA)
230
231    def is_force_2fa(self, username):
232        r = super(UserOptionsManager, self).filter(email=username,
233                                                   option_key=KEY_FORCE_2FA)
234        return True if len(r) > 0 else False
235
236    def set_user_logged_in(self, username):
237        return self.set_user_option(username, KEY_USER_LOGGED_IN,
238                                    VAL_USER_LOGGED_IN)
239
240    def is_user_logged_in(self, username):
241        """Check whether user has logged in successfully at least once.
242        """
243        try:
244            r = super(UserOptionsManager, self).get(
245                email=username, option_key=KEY_USER_LOGGED_IN)
246            return r.option_val == VAL_USER_LOGGED_IN
247        except UserOptions.DoesNotExist:
248            return False
249
250    def set_webdav_secret(self, username, secret):
251        return self.set_user_option(username, KEY_WEBDAV_SECRET,
252                                    secret)
253
254    def unset_webdav_secret(self, username):
255        return self.unset_user_option(username, KEY_WEBDAV_SECRET)
256
257    def get_webdav_secret(self, username):
258        try:
259            r = super(UserOptionsManager, self).get(
260                email=username, option_key=KEY_WEBDAV_SECRET
261            )
262            return r.option_val
263        except UserOptions.DoesNotExist:
264            return None
265
266    def get_webdav_decoded_secret(self, username):
267        from seahub.utils.hasher import AESPasswordHasher
268
269        secret = UserOptions.objects.get_webdav_secret(username)
270        if secret:
271            aes = AESPasswordHasher()
272            decoded = aes.decode(secret)
273        else:
274            decoded = None
275        return decoded
276
277    def set_file_updates_email_interval(self, username, seconds):
278        return self.set_user_option(username, KEY_FILE_UPDATES_EMAIL_INTERVAL,
279                                    str(seconds))
280
281    def get_file_updates_email_interval(self, username):
282        val = self.get_user_option(username, KEY_FILE_UPDATES_EMAIL_INTERVAL)
283        if not val:
284            return None
285        try:
286            return int(val)
287        except ValueError:
288            logger.error('Failed to convert string %s to int' % val)
289            return None
290
291    def unset_file_updates_email_interval(self, username):
292        return self.unset_user_option(username, KEY_FILE_UPDATES_EMAIL_INTERVAL)
293
294    def set_file_updates_last_emailed_time(self, username, time_dt):
295        return self.set_user_option(
296            username, KEY_FILE_UPDATES_LAST_EMAILED_TIME,
297            time_dt.strftime("%Y-%m-%d %H:%M:%S"))
298
299    def get_file_updates_last_emailed_time(self, username):
300        val = self.get_user_option(username, KEY_FILE_UPDATES_LAST_EMAILED_TIME)
301        if not val:
302            return None
303
304        try:
305            return datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
306        except Exception:
307            logger.error('Failed to convert string %s to datetime obj' % val)
308            return None
309
310    def unset_file_updates_last_emailed_time(self, username):
311        return self.unset_user_option(username, KEY_FILE_UPDATES_LAST_EMAILED_TIME)
312
313    def set_collaborate_email_interval(self, username, seconds):
314        return self.set_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL,
315                                    str(seconds))
316
317    def get_collaborate_email_interval(self, username):
318        val = self.get_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL)
319        if not val:
320            return None
321        try:
322            return int(val)
323        except ValueError:
324            logger.error('Failed to convert string %s to int' % val)
325            return None
326
327    def unset_collaborate_email_interval(self, username):
328        return self.unset_user_option(username, KEY_COLLABORATE_EMAIL_INTERVAL)
329
330    def set_collaborate_last_emailed_time(self, username, time_dt):
331        return self.set_user_option(
332            username, KEY_COLLABORATE_LAST_EMAILED_TIME,
333            time_dt.strftime("%Y-%m-%d %H:%M:%S"))
334
335    def get_collaborate_last_emailed_time(self, username):
336        val = self.get_user_option(username, KEY_COLLABORATE_LAST_EMAILED_TIME)
337        if not val:
338            return None
339
340        try:
341            return datetime.strptime(val, "%Y-%m-%d %H:%M:%S")
342        except Exception:
343            logger.error('Failed to convert string %s to datetime obj' % val)
344            return None
345
346    def unset_collaborate_last_emailed_time(self, username):
347        return self.unset_user_option(username, KEY_COLLABORATE_LAST_EMAILED_TIME)
348
349
350class UserOptions(models.Model):
351    email = LowerCaseCharField(max_length=255, db_index=True)
352    option_key = models.CharField(max_length=50, db_index=True)
353    option_val = models.CharField(max_length=50)
354
355    objects = UserOptionsManager()
356