1# Copyright (c) 2012-2016 Seafile Ltd.
2# encoding: utf-8
3import re
4import logging
5
6from django import forms
7from django.core.mail import send_mail
8from django.utils import translation
9from django.utils.encoding import smart_text
10from django.utils.translation import ugettext_lazy as _
11from django.conf import settings
12from django.contrib.sites.shortcuts import get_current_site
13from seaserv import ccnet_threaded_rpc, unset_repo_passwd, \
14    seafile_api, ccnet_api
15from constance import config
16from registration import signals
17
18from seahub.auth import login
19from seahub.constants import DEFAULT_USER, DEFAULT_ORG, DEFAULT_ADMIN
20from seahub.profile.models import Profile, DetailedProfile
21from seahub.role_permissions.models import AdminRole
22from seahub.role_permissions.utils import get_enabled_role_permissions_by_role, \
23        get_enabled_admin_role_permissions_by_role
24from seahub.utils import is_user_password_strong, get_site_name, \
25    clear_token, get_system_admins, is_pro_version, IS_EMAIL_CONFIGURED
26from seahub.utils.mail import send_html_email_with_dj_template
27from seahub.utils.licenseparse import user_number_over_limit
28from seahub.share.models import ExtraSharePermission
29
30try:
31    from seahub.settings import CLOUD_MODE
32except ImportError:
33    CLOUD_MODE = False
34try:
35    from seahub.settings import MULTI_TENANCY
36except ImportError:
37    MULTI_TENANCY = False
38
39logger = logging.getLogger(__name__)
40
41ANONYMOUS_EMAIL = 'Anonymous'
42
43UNUSABLE_PASSWORD = '!'  # This will never be a valid hash
44
45
46class UserManager(object):
47
48    def create_user(self, email, password=None, is_staff=False, is_active=False):
49        """
50        Creates and saves a User with given username and password.
51        """
52        # Lowercasing email address to avoid confusion.
53        email = email.lower()
54
55        user = User(email=email)
56        user.is_staff = is_staff
57        user.is_active = is_active
58        user.set_password(password)
59        user.save()
60
61        return self.get(email=email)
62
63    def update_role(self, email, role):
64        """
65        If user has a role, update it; or create a role for user.
66        """
67        ccnet_api.update_role_emailuser(email, role)
68        return self.get(email=email)
69
70    def create_superuser(self, email, password):
71        u = self.create_user(email, password, is_staff=True, is_active=True)
72        return u
73
74    def get_superusers(self):
75        """Return a list of admins.
76        """
77        emailusers = ccnet_threaded_rpc.get_superusers()
78
79        user_list = []
80        for e in emailusers:
81            user = User(e.email)
82            user.id = e.id
83            user.is_staff = e.is_staff
84            user.is_active = e.is_active
85            user.ctime = e.ctime
86            user_list.append(user)
87
88        return user_list
89
90    def get(self, email=None, id=None):
91        if not email and not id:
92            raise User.DoesNotExist('User matching query does not exits.')
93
94        if email:
95            emailuser = ccnet_threaded_rpc.get_emailuser(email)
96        if id:
97            emailuser = ccnet_threaded_rpc.get_emailuser_by_id(id)
98        if not emailuser:
99            raise User.DoesNotExist('User matching query does not exits.')
100
101        user = User(emailuser.email)
102        user.id = emailuser.id
103        user.enc_password = emailuser.password
104        user.is_staff = emailuser.is_staff
105        user.is_active = emailuser.is_active
106        user.ctime = emailuser.ctime
107        user.org = emailuser.org
108        user.source = emailuser.source
109        user.role = emailuser.role
110        user.reference_id = emailuser.reference_id
111
112        if user.is_staff:
113            try:
114                role_obj = AdminRole.objects.get_admin_role(emailuser.email)
115                admin_role = role_obj.role
116            except AdminRole.DoesNotExist:
117                admin_role = DEFAULT_ADMIN
118
119            user.admin_role = admin_role
120        else:
121            user.admin_role = ''
122
123        return user
124
125
126class UserPermissions(object):
127    def __init__(self, user):
128        self.user = user
129
130    def _get_user_role(self):
131        org_role = self.user.org_role
132        if org_role is None:
133            return self.user.role
134
135        if self.user.role == '' or self.user.role == DEFAULT_USER:
136            if org_role == DEFAULT_ORG:
137                return DEFAULT_USER
138            else:
139                return org_role
140        else:
141            return self.user.role
142
143    def _get_perm_by_roles(self, perm_name):
144        role = self._get_user_role()
145        return get_enabled_role_permissions_by_role(role)[perm_name]
146
147    def can_add_repo(self):
148        return self._get_perm_by_roles('can_add_repo')
149
150    def can_add_group(self):
151        return self._get_perm_by_roles('can_add_group')
152
153    def can_generate_share_link(self):
154        return self._get_perm_by_roles('can_generate_share_link')
155
156    def can_generate_upload_link(self):
157        return self._get_perm_by_roles('can_generate_upload_link')
158
159    def can_use_global_address_book(self):
160        return self._get_perm_by_roles('can_use_global_address_book')
161
162    def can_view_org(self):
163        if MULTI_TENANCY:
164            return True if self.user.org is not None else False
165
166        if CLOUD_MODE:
167            return False
168
169        return self._get_perm_by_roles('can_view_org')
170
171    def can_add_public_repo(self):
172        """ Check if user can create public repo or share existed repo to public.
173
174        Used when MULTI_TENANCY feature is NOT enabled.
175        """
176
177        if CLOUD_MODE:
178            if MULTI_TENANCY:
179                return True
180            else:
181                return False
182        elif self.user.is_staff:
183            return True
184        elif self._get_perm_by_roles('can_add_public_repo') and \
185                bool(config.ENABLE_USER_CREATE_ORG_REPO):
186            return True
187        else:
188            return False
189
190    def can_drag_drop_folder_to_sync(self):
191        return self._get_perm_by_roles('can_drag_drop_folder_to_sync')
192
193    def can_connect_with_android_clients(self):
194        return self._get_perm_by_roles('can_connect_with_android_clients')
195
196    def can_connect_with_ios_clients(self):
197        return self._get_perm_by_roles('can_connect_with_ios_clients')
198
199    def can_connect_with_desktop_clients(self):
200        return self._get_perm_by_roles('can_connect_with_desktop_clients')
201
202    def can_invite_guest(self):
203        return self._get_perm_by_roles('can_invite_guest')
204
205    def can_export_files_via_mobile_client(self):
206        return self._get_perm_by_roles('can_export_files_via_mobile_client')
207
208    def role_quota(self):
209        return self._get_perm_by_roles('role_quota')
210
211    def can_send_share_link_mail(self):
212        if not IS_EMAIL_CONFIGURED:
213            return False
214
215        return self._get_perm_by_roles('can_send_share_link_mail')
216
217    def storage_ids(self):
218        return self._get_perm_by_roles('storage_ids')
219
220    def can_publish_repo(self):
221        if not settings.ENABLE_WIKI:
222            return False
223
224        return self._get_perm_by_roles('can_publish_repo')
225
226
227class AdminPermissions(object):
228    def __init__(self, user):
229        self.user = user
230
231    def can_view_system_info(self):
232        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_view_system_info']
233
234    def can_view_statistic(self):
235        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_view_statistic']
236
237    def can_config_system(self):
238        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_config_system']
239
240    def can_manage_library(self):
241        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_manage_library']
242
243    def can_manage_user(self):
244        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_manage_user']
245
246    def can_update_user(self):
247        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_update_user']
248
249    def can_manage_group(self):
250        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_manage_group']
251
252    def can_view_user_log(self):
253        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_view_user_log']
254
255    def can_view_admin_log(self):
256        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['can_view_admin_log']
257
258    def other_permission(self):
259        return get_enabled_admin_role_permissions_by_role(self.user.admin_role)['other_permission']
260
261
262class User(object):
263    is_staff = False
264    is_active = False
265    is_superuser = False
266    groups = []
267    org = None
268    objects = UserManager()
269
270    @property
271    def org_role(self):
272        if not MULTI_TENANCY:
273            return None
274
275        if not hasattr(self, '_cached_orgs'):
276            self._cached_orgs = ccnet_api.get_orgs_by_user(self.username)
277
278        if not self._cached_orgs:
279            return None
280
281        if not hasattr(self, '_cached_org_role'):
282            from seahub_extra.organizations.models import OrgSettings
283            self._cached_org_role = OrgSettings.objects.get_role_by_org(
284                self._cached_orgs[0])
285
286        return self._cached_org_role
287
288    @property
289    def contact_email(self):
290        if not hasattr(self, '_cached_contact_email'):
291            self._cached_contact_email = email2contact_email(self.username)
292
293        return self._cached_contact_email
294
295    @property
296    def name(self):
297        if not hasattr(self, '_cached_nickname'):
298            # convert raw string to unicode obj
299            self._cached_nickname = smart_text(email2nickname(self.username))
300
301        return self._cached_nickname
302
303    class DoesNotExist(Exception):
304        pass
305
306    def __init__(self, email):
307        self.username = email
308        self.email = email
309        self.permissions = UserPermissions(self)
310        self.admin_permissions = AdminPermissions(self)
311
312    def __unicode__(self):
313        return self.username
314
315    @property
316    def is_anonymous(self):
317        """
318        Always returns False. This is a way of comparing User objects to
319        anonymous users.
320        """
321        return False
322
323    @property
324    def is_authenticated(self):
325        """
326        Always return True. This is a way to tell if the user has been
327        authenticated in templates.
328        """
329        return True
330
331    def save(self):
332        emailuser = ccnet_api.get_emailuser(self.username)
333        if emailuser and emailuser.source.lower() in ("db", "ldapimport"):
334            if not hasattr(self, 'password'):
335                self.set_unusable_password()
336
337            if emailuser.source == "DB":
338                source = "DB"
339            else:
340                source = "LDAP"
341
342            if not self.is_active:
343                # clear web api and repo sync token
344                # when inactive an user
345                try:
346                    clear_token(self.username)
347                except Exception as e:
348                    logger.error(e)
349
350            result_code = ccnet_threaded_rpc.update_emailuser(source,
351                                                              emailuser.id,
352                                                              self.password,
353                                                              int(self.is_staff),
354                                                              int(self.is_active))
355        else:
356            result_code = ccnet_threaded_rpc.add_emailuser(self.username,
357                                                           self.password,
358                                                           int(self.is_staff),
359                                                           int(self.is_active))
360        # -1 stands for failed; 0 stands for success
361        return result_code
362
363    def delete(self):
364        """
365        When delete user, we should also delete group relationships.
366        """
367        if self.source == "DB":
368            source = "DB"
369        else:
370            source = "LDAP"
371
372        username = self.username
373
374        orgs = []
375        if is_pro_version():
376            orgs = ccnet_api.get_orgs_by_user(username)
377
378        # remove owned repos
379        owned_repos = []
380        if orgs:
381            for org in orgs:
382                owned_repos += seafile_api.get_org_owned_repo_list(org.org_id,
383                                                                   username)
384        else:
385            owned_repos += seafile_api.get_owned_repo_list(username)
386
387        for r in owned_repos:
388            seafile_api.remove_repo(r.id)
389
390        # remove shared in repos
391        shared_in_repos = []
392        if orgs:
393            for org in orgs:
394                org_id = org.org_id
395                shared_in_repos = seafile_api.get_org_share_in_repo_list(org_id, username, -1, -1)
396
397                for r in shared_in_repos:
398                    seafile_api.org_remove_share(org_id, r.repo_id, r.user, username)
399        else:
400            shared_in_repos = seafile_api.get_share_in_repo_list(username, -1, -1)
401            for r in shared_in_repos:
402                seafile_api.remove_share(r.repo_id, r.user, username)
403        ExtraSharePermission.objects.filter(share_to=username).delete()
404
405        # clear web api and repo sync token
406        # when delete user
407        try:
408            clear_token(self.username)
409        except Exception as e:
410            logger.error(e)
411
412        # remove current user from joined groups
413        ccnet_api.remove_group_user(username)
414
415        ccnet_api.remove_emailuser(source, username)
416        signals.user_deleted.send(sender=self.__class__, username=username)
417
418        Profile.objects.delete_profile_by_user(username)
419        if config.ENABLE_TERMS_AND_CONDITIONS:
420            from termsandconditions.models import UserTermsAndConditions
421            UserTermsAndConditions.objects.filter(username=username).delete()
422        self.delete_user_options(username)
423
424    def get_username(self):
425        return self.username
426
427    def delete_user_options(self, username):
428        """Remove user's all options.
429        """
430        from seahub.options.models import UserOptions
431        UserOptions.objects.filter(email=username).delete()
432
433    def get_and_delete_messages(self):
434        messages = []
435        return messages
436
437    def set_password(self, raw_password):
438        if raw_password is None:
439            self.set_unusable_password()
440        else:
441            self.password = '%s' % raw_password
442
443        # clear web api and repo sync token
444        # when user password change
445        try:
446            clear_token(self.username)
447        except Exception as e:
448            logger.error(e)
449
450    def check_password(self, raw_password):
451        """
452        Returns a boolean of whether the raw_password was correct. Handles
453        encryption formats behind the scenes.
454        """
455        # Backwards-compatibility check. Older passwords won't include the
456        # algorithm or salt.
457
458        # if '$' not in self.password:
459        #     is_correct = (self.password == \
460        #                       get_hexdigest('sha1', '', raw_password))
461        #     return is_correct
462        return (ccnet_threaded_rpc.validate_emailuser(self.username, raw_password) == 0)
463
464    def set_unusable_password(self):
465        # Sets a value that will never be a valid hash
466        self.password = UNUSABLE_PASSWORD
467
468    def email_user(self, subject, message, from_email=None):
469        "Sends an e-mail to this User."
470        send_mail(subject, message, from_email, [self.email])
471
472    def freeze_user(self, notify_admins=False):
473        self.is_active = False
474        self.save()
475
476        if notify_admins:
477            admins = get_system_admins()
478            for u in admins:
479                # save current language
480                cur_language = translation.get_language()
481
482                # get and active user language
483                user_language = Profile.objects.get_user_language(u.email)
484                translation.activate(user_language)
485
486                send_html_email_with_dj_template(u.email,
487                                                 subject=_('Account %(account)s froze on %(site)s.') % {
488                                                     "account": self.email,
489                                                     "site": get_site_name()},
490                                                 dj_template='sysadmin/user_freeze_email.html',
491                                                 context={'user': self.email})
492
493                # restore current language
494                translation.activate(cur_language)
495
496    def remove_repo_passwds(self):
497        """
498        Remove all repo decryption passwords stored on server.
499        """
500        from seahub.utils import get_user_repos
501        owned_repos, shared_repos, groups_repos, public_repos = get_user_repos(self.email)
502
503        def has_repo(repos, repo):
504            for r in repos:
505                if repo.id == r.id:
506                    return True
507            return False
508
509        passwd_setted_repos = []
510        for r in owned_repos + shared_repos + groups_repos + public_repos:
511            if not has_repo(passwd_setted_repos, r) and r.encrypted and \
512                    seafile_api.is_password_set(r.id, self.email):
513                passwd_setted_repos.append(r)
514
515        for r in passwd_setted_repos:
516            unset_repo_passwd(r.id, self.email)
517
518    def remove_org_repo_passwds(self, org_id):
519        """
520        Remove all org repo decryption passwords stored on server.
521        """
522        from seahub.utils import get_user_repos
523        owned_repos, shared_repos, groups_repos, public_repos = get_user_repos(self.email, org_id=org_id)
524
525        def has_repo(repos, repo):
526            for r in repos:
527                if repo.id == r.id:
528                    return True
529            return False
530
531        passwd_setted_repos = []
532        for r in owned_repos + shared_repos + groups_repos + public_repos:
533            if not has_repo(passwd_setted_repos, r) and r.encrypted and \
534                    seafile_api.is_password_set(r.id, self.email):
535                passwd_setted_repos.append(r)
536
537        for r in passwd_setted_repos:
538            unset_repo_passwd(r.id, self.email)
539
540
541class AuthBackend(object):
542
543    def get_user_with_import(self, username):
544        emailuser = ccnet_api.get_emailuser_with_import(username)
545        if not emailuser:
546            raise User.DoesNotExist('User matching query does not exits.')
547
548        user = User(emailuser.email)
549        user.id = emailuser.id
550        user.enc_password = emailuser.password
551        user.is_staff = emailuser.is_staff
552        user.is_active = emailuser.is_active
553        user.ctime = emailuser.ctime
554        user.org = emailuser.org
555        user.source = emailuser.source
556        user.role = emailuser.role
557
558        if user.is_staff:
559            try:
560                role_obj = AdminRole.objects.get_admin_role(emailuser.email)
561                admin_role = role_obj.role
562            except AdminRole.DoesNotExist:
563                admin_role = DEFAULT_ADMIN
564
565            user.admin_role = admin_role
566        else:
567            user.admin_role = ''
568
569        return user
570
571    def get_user(self, username):
572        try:
573            user = self.get_user_with_import(username)
574        except User.DoesNotExist:
575            user = None
576        return user
577
578    def authenticate(self, username=None, password=None):
579        user = self.get_user(username)
580        if not user:
581            return None
582
583        if user.check_password(password):
584            return user
585
586
587# Register related
588class RegistrationBackend(object):
589    """
590    A registration backend which follows a simple workflow:
591
592    1. User signs up, inactive account is created.
593
594    2. Email is sent to user with activation link.
595
596    3. User clicks activation link, account is now active.
597
598    Using this backend requires that
599
600    * ``registration`` be listed in the ``INSTALLED_APPS`` setting
601      (since this backend makes use of models defined in this
602      application).
603
604    * The setting ``ACCOUNT_ACTIVATION_DAYS`` be supplied, specifying
605      (as an integer) the number of days from registration during
606      which a user may activate their account (after that period
607      expires, activation will be disallowed).
608
609    * The creation of the templates
610      ``registration/activation_email_subject.txt`` and
611      ``registration/activation_email.txt``, which will be used for
612      the activation email. See the notes for this backends
613      ``register`` method for details regarding these templates.
614
615    Additionally, registration can be temporarily closed by adding the
616    setting ``REGISTRATION_OPEN`` and setting it to
617    ``False``. Omitting this setting, or setting it to ``True``, will
618    be interpreted as meaning that registration is currently open and
619    permitted.
620
621    Internally, this is accomplished via storing an activation key in
622    an instance of ``registration.models.RegistrationProfile``. See
623    that model and its custom manager for full documentation of its
624    fields and supported operations.
625
626    """
627    def register(self, request, **kwargs):
628        """
629        Given a username, email address and password, register a new
630        user account, which will initially be inactive.
631
632        Along with the new ``User`` object, a new
633        ``registration.models.RegistrationProfile`` will be created,
634        tied to that ``User``, containing the activation key which
635        will be used for this account.
636
637        An email will be sent to the supplied email address; this
638        email should contain an activation link. The email will be
639        rendered using two templates. See the documentation for
640        ``RegistrationProfile.send_activation_email()`` for
641        information about these templates and the contexts provided to
642        them.
643
644        After the ``User`` and ``RegistrationProfile`` are created and
645        the activation email is sent, the signal
646        ``registration.signals.user_registered`` will be sent, with
647        the new ``User`` as the keyword argument ``user`` and the
648        class of this backend as the sender.
649
650        """
651        email, password = kwargs['email'], kwargs['password1']
652        username = email
653        site = get_current_site(request)
654
655        from registration.models import RegistrationProfile
656        if bool(config.ACTIVATE_AFTER_REGISTRATION) is True:
657            # since user will be activated after registration,
658            # so we will not use email sending, just create acitvated user
659            new_user = RegistrationProfile.objects.create_active_user(username, email,
660                                                                      password, site,
661                                                                      send_email=False)
662            # login the user
663            new_user.backend = settings.AUTHENTICATION_BACKENDS[0]
664
665            login(request, new_user)
666        else:
667            # create inactive user, user can be activated by admin, or through activated email
668            new_user = RegistrationProfile.objects.create_inactive_user(username, email,
669                                                                        password, site,
670                                                                        send_email=config.REGISTRATION_SEND_MAIL)
671
672        # userid = kwargs['userid']
673        # if userid:
674        #     ccnet_threaded_rpc.add_binding(new_user.username, userid)
675
676        if settings.REQUIRE_DETAIL_ON_REGISTRATION:
677            name = kwargs.get('name', '')
678            department = kwargs.get('department', '')
679            telephone = kwargs.get('telephone', '')
680            note = kwargs.get('note', '')
681            Profile.objects.add_or_update(new_user.username, name, note)
682            DetailedProfile.objects.add_detailed_profile(new_user.username,
683                                                         department,
684                                                         telephone)
685
686        signals.user_registered.send(sender=self.__class__,
687                                     user=new_user,
688                                     request=request)
689        return new_user
690
691    def activate(self, request, activation_key):
692        """
693        Given an an activation key, look up and activate the user
694        account corresponding to that key (if possible).
695
696        After successful activation, the signal
697        ``registration.signals.user_activated`` will be sent, with the
698        newly activated ``User`` as the keyword argument ``user`` and
699        the class of this backend as the sender.
700
701        """
702        from registration.models import RegistrationProfile
703        activated = RegistrationProfile.objects.activate_user(activation_key)
704        if activated:
705            signals.user_activated.send(sender=self.__class__,
706                                        user=activated,
707                                        request=request)
708            # login the user
709            activated.backend = settings.AUTHENTICATION_BACKENDS[0]
710            login(request, activated)
711
712        return activated
713
714    def registration_allowed(self, request):
715        """
716        Indicate whether account registration is currently permitted,
717        based on the value of the setting ``REGISTRATION_OPEN``. This
718        is determined as follows:
719
720        * If ``REGISTRATION_OPEN`` is not specified in settings, or is
721          set to ``True``, registration is permitted.
722
723        * If ``REGISTRATION_OPEN`` is both specified and set to
724          ``False``, registration is not permitted.
725
726        """
727        return getattr(settings, 'REGISTRATION_OPEN', True)
728
729    def get_form_class(self, request):
730        """
731        Return the default form class used for user registration.
732
733        """
734        return RegistrationForm
735
736    def post_registration_redirect(self, request, user):
737        """
738        Return the name of the URL to redirect to after successful
739        user registration.
740
741        """
742        return ('registration_complete', (), {})
743
744    def post_activation_redirect(self, request, user):
745        """
746        Return the name of the URL to redirect to after successful
747        account activation.
748
749        """
750        return ('libraries', (), {})
751
752
753class RegistrationForm(forms.Form):
754    """
755    Form for registering a new user account.
756
757    Validates that the requested email is not already in use, and
758    requires the password to be entered twice to catch typos.
759    """
760    attrs_dict = {'class': 'input'}
761
762    email = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=75)),
763                            label=_("Email address"))
764
765    userid = forms.RegexField(regex=r'^\w+$',
766                              max_length=40,
767                              required=False,
768                              widget=forms.TextInput(),
769                              label=_("Username"),
770                              error_messages={'invalid': _("This value must be of length 40")})
771
772    password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
773                                label=_("Password"))
774    password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
775                                label=_("Password (again)"))
776
777    @classmethod
778    def allow_register(self, email):
779        prog = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)",
780                          re.IGNORECASE)
781        return False if prog.match(email) is None else True
782
783    def clean_email(self):
784        if user_number_over_limit():
785            raise forms.ValidationError(_("The number of users exceeds the limit."))
786
787        email = self.cleaned_data['email']
788        if not self.allow_register(email):
789            raise forms.ValidationError(_("Enter a valid email address."))
790
791        emailuser = ccnet_threaded_rpc.get_emailuser(email)
792        if not emailuser:
793            return self.cleaned_data['email']
794        else:
795            raise forms.ValidationError(_("User %s already exists.") % email)
796
797    def clean_userid(self):
798        if self.cleaned_data['userid'] and len(self.cleaned_data['userid']) != 40:
799            raise forms.ValidationError(_("Invalid user id."))
800        return self.cleaned_data['userid']
801
802    def clean_password1(self):
803        if 'password1' in self.cleaned_data:
804            pwd = self.cleaned_data['password1']
805
806            if bool(config.USER_STRONG_PASSWORD_REQUIRED) is True:
807                if bool(is_user_password_strong(pwd)) is True:
808                    return pwd
809                else:
810                    raise forms.ValidationError(
811                        _(("%(pwd_len)s characters or more, include "
812                           "%(num_types)s types or more of these: "
813                           "letters(case sensitive), numbers, and symbols")) %
814                        {'pwd_len': config.USER_PASSWORD_MIN_LENGTH,
815                         'num_types': config.USER_PASSWORD_STRENGTH_LEVEL})
816            else:
817                return pwd
818
819    def clean_password2(self):
820        """
821        Verifiy that the values entered into the two password fields
822        match. Note that an error here will end up in
823        ``non_field_errors()`` because it doesn't apply to a single
824        field.
825
826        """
827        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
828            if self.cleaned_data['password1'] != self.cleaned_data['password2']:
829                raise forms.ValidationError(_("The two password fields didn't match."))
830        return self.cleaned_data
831
832
833class DetailedRegistrationForm(RegistrationForm):
834    attrs_dict = {'class': 'input'}
835
836    try:
837        from seahub.settings import REGISTRATION_DETAILS_MAP
838    except ImportError:
839        REGISTRATION_DETAILS_MAP = None
840
841    if REGISTRATION_DETAILS_MAP:
842        name_required = REGISTRATION_DETAILS_MAP.get('name', False)
843        dept_required = REGISTRATION_DETAILS_MAP.get('department', False)
844        tele_required = REGISTRATION_DETAILS_MAP.get('telephone', False)
845        note_required = REGISTRATION_DETAILS_MAP.get('note', False)
846    else:
847        # Backward compatible
848        name_required = dept_required = tele_required = note_required = True
849
850    name = forms.CharField(widget=forms.TextInput(
851            attrs=dict(attrs_dict, maxlength=64)), label=_("name"),
852                           required=name_required)
853    department = forms.CharField(widget=forms.TextInput(
854            attrs=dict(attrs_dict, maxlength=512)), label=_("department"),
855                                 required=dept_required)
856    telephone = forms.CharField(widget=forms.TextInput(
857            attrs=dict(attrs_dict, maxlength=100)), label=_("telephone"),
858                                required=tele_required)
859    note = forms.CharField(widget=forms.TextInput(
860            attrs=dict(attrs_dict, maxlength=100)), label=_("note"),
861                           required=note_required)
862
863# Move here to avoid circular import
864from seahub.base.templatetags.seahub_tags import email2nickname, \
865    email2contact_email
866