class GeneralParametersForm(param_forms.AdminParametersForm): """General parameters.""" app = "core" sep1 = SeparatorField(label=ugettext_lazy("Authentication")) authentication_type = forms.ChoiceField( label=ugettext_lazy("Authentication type"), choices=[('local', ugettext_lazy("Local")), ('ldap', "LDAP")], initial="local", help_text=ugettext_lazy("The backend used for authentication"), widget=InlineRadioSelect) password_scheme = forms.ChoiceField( label=ugettext_lazy("Default password scheme"), choices=[("sha512crypt", "sha512crypt"), ("sha256crypt", "sha256crypt"), ("blfcrypt", "bcrypt"), ("md5crypt", ugettext_lazy("md5crypt (weak)")), ("sha256", ugettext_lazy("sha256 (weak)")), ("md5", ugettext_lazy("md5 (weak)")), ("crypt", ugettext_lazy("crypt (weak)")), ("plain", ugettext_lazy("plain (weak)"))], initial="sha512crypt", help_text=ugettext_lazy("Scheme used to crypt mailbox passwords"), widget=forms.Select(attrs={"class": "form-control"})) rounds_number = forms.IntegerField( label=ugettext_lazy("Rounds"), initial=70000, help_text=ugettext_lazy( "Number of rounds to use (only used by sha256crypt and " "sha512crypt). Must be between 1000 and 999999999, inclusive."), widget=forms.TextInput(attrs={"class": "form-control"})) secret_key = forms.CharField( label=ugettext_lazy("Secret key"), initial=random_key(), help_text=ugettext_lazy("Key used to encrypt/decrypt passwords"), widget=forms.TextInput(attrs={"class": "form-control"})) default_password = forms.CharField( label=ugettext_lazy("Default password"), initial="password", help_text=ugettext_lazy( "Default password for automatically created accounts.")) random_password_length = forms.IntegerField( label=ugettext_lazy("Random password length"), min_value=8, initial=8, help_text=ugettext_lazy("Length of randomly generated passwords.")) # LDAP specific settings ldap_sep = SeparatorField(label=ugettext_lazy("LDAP settings")) ldap_server_address = forms.CharField( label=ugettext_lazy("Server address"), initial="localhost", help_text=ugettext_lazy( "The IP address or the DNS name of the LDAP server"), widget=forms.TextInput(attrs={"class": "form-control"})) ldap_server_port = forms.IntegerField( label=ugettext_lazy("Server port"), initial=389, help_text=ugettext_lazy("The TCP port number used by the LDAP server"), widget=forms.TextInput(attrs={"class": "form-control"})) ldap_secured = YesNoField( label=ugettext_lazy("Use a secured connection"), initial=False, help_text=ugettext_lazy( "Use an SSL/TLS connection to access the LDAP server")) ldap_auth_method = forms.ChoiceField( label=ugettext_lazy("Authentication method"), choices=[('searchbind', ugettext_lazy("Search and bind")), ('directbind', ugettext_lazy("Direct bind"))], initial='searchbind', help_text=ugettext_lazy("Choose the authentication method to use"), widget=forms.Select(attrs={"class": "form-control"})) ldap_bind_dn = forms.CharField( label=ugettext_lazy("Bind DN"), initial='', help_text=ugettext_lazy( "The distinguished name to use when binding to the LDAP server. " "Leave empty for an anonymous bind"), required=False, widget=forms.TextInput(attrs={"class": "form-control"})) ldap_bind_password = forms.CharField( label=ugettext_lazy("Bind password"), initial='', help_text=ugettext_lazy( "The password to use when binding to the LDAP server " "(with 'Bind DN')"), widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=True), required=False) ldap_search_base = forms.CharField( label=ugettext_lazy("Users search base"), initial="", help_text=ugettext_lazy( "The distinguished name of the search base used to find users"), required=False, widget=forms.TextInput(attrs={"class": "form-control"})) ldap_search_filter = forms.CharField( label=ugettext_lazy("Search filter"), initial="(mail=%(user)s)", help_text=ugettext_lazy( "An optional filter string (e.g. '(objectClass=person)'). " "In order to be valid, it must be enclosed in parentheses."), required=False, widget=forms.TextInput(attrs={"class": "form-control"})) ldap_user_dn_template = forms.CharField( label=ugettext_lazy("User DN template"), initial="", help_text=ugettext_lazy( "The template used to construct a user's DN. It should contain " "one placeholder (ie. %(user)s)"), required=False, widget=forms.TextInput(attrs={"class": "form-control"})) ldap_password_attribute = forms.CharField( label=ugettext_lazy("Password attribute"), initial="userPassword", help_text=ugettext_lazy("The attribute used to store user passwords"), widget=forms.TextInput(attrs={"class": "form-control"})) ldap_is_active_directory = YesNoField( label=ugettext_lazy("Active Directory"), initial=False, help_text=ugettext_lazy( "Tell if the LDAP server is an Active Directory one")) ldap_admin_groups = forms.CharField( label=ugettext_lazy("Administrator groups"), initial="", help_text=ugettext_lazy( "Members of those LDAP Posix groups will be created as domain " "administrators. Use ';' characters to separate groups."), required=False) ldap_group_type = forms.ChoiceField( label=ugettext_lazy("Group type"), initial="posixgroup", choices=constants.LDAP_GROUP_TYPES, help_text=ugettext_lazy( "The LDAP group type to use with your directory.")) ldap_groups_search_base = forms.CharField( label=ugettext_lazy("Groups search base"), initial="", help_text=ugettext_lazy( "The distinguished name of the search base used to find groups"), required=False) dash_sep = SeparatorField(label=ugettext_lazy("Dashboard")) rss_feed_url = forms.URLField( label=ugettext_lazy("Custom RSS feed"), required=False, help_text=ugettext_lazy( "Display custom RSS feed to resellers and domain administrators")) hide_features_widget = YesNoField( label=ugettext_lazy("Hide features widget"), initial=False, help_text=ugettext_lazy( "Hide features widget for resellers and domain administrators")) notif_sep = SeparatorField(label=ugettext_lazy("Notifications")) sender_address = lib_fields.UTF8EmailField( label=_("Sender address"), initial="*****@*****.**", help_text=_("Email address used to send notifications.")) api_sep = SeparatorField(label=ugettext_lazy("Public API")) enable_api_communication = YesNoField( label=ugettext_lazy("Enable communication"), initial=True, help_text=ugettext_lazy( "Enable communication with Modoboa public API")) check_new_versions = YesNoField( label=ugettext_lazy("Check new versions"), initial=True, help_text=ugettext_lazy( "Automatically checks if a newer version is available")) send_statistics = YesNoField(label=ugettext_lazy("Send statistics"), initial=True, help_text=ugettext_lazy( "Send statistics to Modoboa public API " "(counters and used extensions)")) sep3 = SeparatorField(label=ugettext_lazy("Miscellaneous")) inactive_account_threshold = forms.IntegerField( label=_("Inactive account threshold"), initial=30, help_text=_( "An account with a last login date greater than this threshold " "(in days) will be considered as inactive"), widget=forms.TextInput(attrs={"class": "form-control"})) top_notifications_check_interval = forms.IntegerField( label=_("Top notifications check interval"), initial=30, help_text=_( "Interval between two top notification checks (in seconds)"), widget=forms.TextInput(attrs={"class": "form-control"})) log_maximum_age = forms.IntegerField( label=ugettext_lazy("Maximum log record age"), initial=365, help_text=ugettext_lazy("The maximum age in days of a log record"), widget=forms.TextInput(attrs={"class": "form-control"})) items_per_page = forms.IntegerField( label=ugettext_lazy("Items per page"), initial=30, help_text=ugettext_lazy("Number of displayed items per page"), widget=forms.TextInput(attrs={"class": "form-control"})) default_top_redirection = forms.ChoiceField( label=ugettext_lazy("Default top redirection"), choices=[], initial="user", help_text=ugettext_lazy( "The default redirection used when no application is specified"), widget=forms.Select(attrs={"class": "form-control"})) # Visibility rules visibility_rules = { "ldap_sep": "authentication_type=ldap", "ldap_server_address": "authentication_type=ldap", "ldap_server_port": "authentication_type=ldap", "ldap_secured": "authentication_type=ldap", "ldap_auth_method": "authentication_type=ldap", "ldap_bind_dn": "ldap_auth_method=searchbind", "ldap_bind_password": "******", "ldap_search_base": "ldap_auth_method=searchbind", "ldap_search_filter": "ldap_auth_method=searchbind", "ldap_user_dn_template": "ldap_auth_method=directbind", "ldap_password_attribute": "authentication_type=ldap", "ldap_is_active_directory": "authentication_type=ldap", "ldap_admin_groups": "authentication_type=ldap", "ldap_group_type": "authentication_type=ldap", "ldap_groups_search_base": "authentication_type=ldap", "check_new_versions": "enable_api_communication=True", "send_statistics": "enable_api_communication=True", } def __init__(self, *args, **kwargs): super(GeneralParametersForm, self).__init__(*args, **kwargs) self.fields["default_top_redirection"].choices = enabled_applications() def clean_secret_key(self): if len(self.cleaned_data["secret_key"]) not in [16, 24, 32]: raise forms.ValidationError( _("Key must be either 16, 24, or 32 bytes long")) return self.cleaned_data["secret_key"] def clean_ldap_user_dn_template(self): tpl = self.cleaned_data["ldap_user_dn_template"] try: test = tpl % {"user": "******"} except (KeyError, ValueError): raise forms.ValidationError(_("Invalid syntax")) return tpl def clean_rounds_number(self): value = self.cleaned_data["rounds_number"] if value < 1000 or value > 999999999: raise forms.ValidationError(_("Invalid rounds number")) return value def clean_default_password(self): """Check password complexity.""" value = self.cleaned_data["default_password"] password_validation.validate_password(value) return value def clean(self): """Custom validation method Depending on 'ldap_auth_method' value, we check for different required parameters. """ super(GeneralParametersForm, self).clean() cleaned_data = self.cleaned_data if cleaned_data["authentication_type"] != "ldap": return cleaned_data if cleaned_data["ldap_auth_method"] == "searchbind": required_fields = ["ldap_search_base", "ldap_search_filter"] else: required_fields = ["ldap_user_dn_template"] for f in required_fields: if f not in cleaned_data or cleaned_data[f] == u'': self.add_error(f, _("This field is required")) return cleaned_data def to_django_settings(self): """Apply LDAP related parameters to Django settings. Doing so, we can use the django_auth_ldap module. """ try: import ldap from django_auth_ldap.config import (LDAPSearch, PosixGroupType, GroupOfNamesType) ldap_available = True except ImportError: ldap_available = False values = dict(param_tools.get_global_parameters("core")) if not ldap_available or values["authentication_type"] != "ldap": return if not hasattr(settings, "AUTH_LDAP_USER_ATTR_MAP"): setattr(settings, "AUTH_LDAP_USER_ATTR_MAP", { "first_name": "givenName", "email": "mail", "last_name": "sn" }) ldap_uri = "ldaps://" if values["ldap_secured"] else "ldap://" ldap_uri += "%s:%s" % (values["ldap_server_address"], values["ldap_server_port"]) setattr(settings, "AUTH_LDAP_SERVER_URI", ldap_uri) if values["ldap_group_type"] == "groupofnames": setattr(settings, "AUTH_LDAP_GROUP_TYPE", GroupOfNamesType()) searchfilter = "(objectClass=groupOfNames)" else: setattr(settings, "AUTH_LDAP_GROUP_TYPE", PosixGroupType()) searchfilter = "(objectClass=posixGroup)" setattr( settings, "AUTH_LDAP_GROUP_SEARCH", LDAPSearch(values["ldap_groups_search_base"], ldap.SCOPE_SUBTREE, searchfilter)) if values["ldap_auth_method"] == "searchbind": setattr(settings, "AUTH_LDAP_BIND_DN", values["ldap_bind_dn"]) setattr(settings, "AUTH_LDAP_BIND_PASSWORD", values["ldap_bind_password"]) search = LDAPSearch(values["ldap_search_base"], ldap.SCOPE_SUBTREE, values["ldap_search_filter"]) setattr(settings, "AUTH_LDAP_USER_SEARCH", search) else: setattr(settings, "AUTH_LDAP_USER_DN_TEMPLATE", values["ldap_user_dn_template"]) if values["ldap_is_active_directory"]: if not hasattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS"): setattr(settings, "AUTH_LDAP_GLOBAL_OPTIONS", {ldap.OPT_REFERRALS: False}) else: settings.AUTH_LDAP_GLOBAL_OPTIONS[ldap.OPT_REFERRALS] = False
class AccountFormMail(forms.Form, DynamicForm): """Form to handle mail part.""" email = lib_fields.UTF8EmailField( label=ugettext_lazy("E-mail"), required=False) quota = forms.IntegerField( label=ugettext_lazy("Quota"), required=False, help_text=_("Quota in MB for this mailbox. Define a custom value or " "use domain's default one. Leave empty to define an " "unlimited value (not allowed for domain " "administrators)."), widget=forms.widgets.TextInput(attrs={"class": "form-control"}) ) quota_act = forms.BooleanField(required=False) aliases = lib_fields.UTF8AndEmptyUserEmailField( label=ugettext_lazy("Alias(es)"), required=False, help_text=ugettext_lazy( "Alias(es) of this mailbox. Indicate only one address per input, " "press ENTER to add a new input. To create a catchall alias, just " "enter the domain name (@domain.tld)." ) ) senderaddress = lib_fields.UTF8AndEmptyUserEmailField( label=ugettext_lazy("Sender addresses"), required=False, help_text=ugettext_lazy( "Additional sender address(es) for this account. The user will be " "allowed to send emails using this address, even if it " "does not exist locally. Indicate one address per input. Press " "ENTER to add a new input." ) ) def __init__(self, user, *args, **kwargs): self.mb = kwargs.pop("instance", None) self.user = user super(AccountFormMail, self).__init__(*args, **kwargs) self.field_widths = { "quota": 3 } if self.mb is not None: self.fields["email"].required = True qset = self.mb.aliasrecipient_set.filter(alias__internal=False) for cpt, ralias in enumerate(qset): name = "aliases_{}".format(cpt + 1) self._create_field( lib_fields.UTF8AndEmptyUserEmailField, name, ralias.alias.address) for cpt, saddress in enumerate(self.mb.senderaddress_set.all()): name = "senderaddress_{}".format(cpt + 1) self._create_field( lib_fields.UTF8AndEmptyUserEmailField, name, saddress.address) self.fields["email"].initial = self.mb.full_address self.fields["quota_act"].initial = self.mb.use_domain_quota if not self.mb.use_domain_quota and self.mb.quota: self.fields["quota"].initial = self.mb.quota else: self.fields["quota_act"].initial = True if len(args) and isinstance(args[0], QueryDict): self._load_from_qdict( args[0], "aliases", lib_fields.UTF8AndEmptyUserEmailField) self._load_from_qdict( args[0], "senderaddress", lib_fields.UTF8AndEmptyUserEmailField) def clean_email(self): """Ensure lower case emails""" email = self.cleaned_data["email"].lower() self.locpart, domname = split_mailbox(email) if not domname: return email try: self.domain = models.Domain.objects.get(name=domname) except models.Domain.DoesNotExist: raise forms.ValidationError(_("Domain does not exist")) if not self.mb: try: core_signals.can_create_object.send( sender=self.__class__, context=self.domain, object_type="mailboxes") except lib_exceptions.ModoboaException as inst: raise forms.ValidationError(inst) return email def clean(self): """Custom fields validation. Check if quota is >= 0 only when the domain value is not used. """ cleaned_data = super(AccountFormMail, self).clean() use_default_domain_quota = cleaned_data["quota_act"] condition = ( not use_default_domain_quota and cleaned_data["quota"] is not None and cleaned_data["quota"] < 0) if condition: self.add_error("quota", _("Must be a positive integer")) self.aliases = [] self.sender_addresses = [] for name, value in list(cleaned_data.items()): if value == "": continue if name.startswith("aliases"): local_part, domname = split_mailbox(value) domain = models.Domain.objects.filter(name=domname).first() if not domain: self.add_error(name, _("Local domain does not exist")) continue if not self.user.can_access(domain): self.add_error( name, _("You don't have access to this domain")) continue self.aliases.append(value.lower()) elif name.startswith("senderaddress"): local_part, domname = split_mailbox(value) domain = models.Domain.objects.filter(name=domname).first() if domain and not self.user.can_access(domain): self.add_error( name, _("You don't have access to this domain")) continue self.sender_addresses.append(value.lower()) return cleaned_data def create_mailbox(self, user, account): """Create a mailbox associated to :kw:`account`.""" if not user.can_access(self.domain): raise lib_exceptions.PermDeniedException core_signals.can_create_object.send( self.__class__, context=user, klass=models.Mailbox) self.mb = models.Mailbox( address=self.locpart, domain=self.domain, user=account, use_domain_quota=self.cleaned_data["quota_act"]) self.mb.set_quota(self.cleaned_data["quota"], user.has_perm("admin.add_domain")) self.mb.save(creator=user) def _update_aliases(self, user, account): """Update mailbox aliases.""" qset = self.mb.aliasrecipient_set.select_related("alias").filter( alias__internal=False) for ralias in qset: if ralias.alias.address not in self.aliases: alias = ralias.alias ralias.delete() if alias.recipients_count > 0: continue alias.delete() else: self.aliases.remove(ralias.alias.address) if not self.aliases: return core_signals.can_create_object.send( self.__class__, context=user, klass=models.Alias, count=len(self.aliases)) core_signals.can_create_object.send( self.__class__, context=self.mb.domain, object_type="mailbox_aliases", count=len(self.aliases)) for alias in self.aliases: if self.mb.aliasrecipient_set.select_related("alias").filter( alias__address=alias).exists(): continue local_part, domname = split_mailbox(alias) al = models.Alias(address=alias, enabled=account.is_active) al.domain = models.Domain.objects.get(name=domname) al.save() al.set_recipients([self.mb.full_address]) al.post_create(user) def _update_sender_addresses(self): """Update mailbox sender addresses.""" for saddress in self.mb.senderaddress_set.all(): if saddress.address not in self.sender_addresses: saddress.delete() else: self.sender_addresses.remove(saddress.address) if not len(self.sender_addresses): return to_create = [] for saddress in self.sender_addresses: to_create.append( models.SenderAddress(address=saddress, mailbox=self.mb)) models.SenderAddress.objects.bulk_create(to_create) def save(self, user, account): """Save or update account mailbox.""" if self.cleaned_data["email"] == "": return None if self.cleaned_data["quota_act"]: self.cleaned_data["quota"] = None if not hasattr(self, "mb") or self.mb is None: self.create_mailbox(user, account) else: self.cleaned_data["use_domain_quota"] = ( self.cleaned_data["quota_act"]) self.mb.update_from_dict(user, self.cleaned_data) account.email = self.cleaned_data["email"] account.save() self._update_aliases(user, account) self._update_sender_addresses() return self.mb
class GeneralParametersForm(param_forms.AdminParametersForm): """General parameters.""" app = "core" sep1 = SeparatorField(label=ugettext_lazy("Authentication")) authentication_type = forms.ChoiceField( label=ugettext_lazy("Authentication type"), choices=[("local", ugettext_lazy("Local")), ("ldap", "LDAP")], initial="local", help_text=ugettext_lazy("The backend used for authentication"), widget=HorizontalRadioSelect()) password_scheme = forms.ChoiceField( label=ugettext_lazy("Default password scheme"), choices=[(hasher.name, ugettext_lazy(hasher.label)) for hasher in PasswordHasher.get_password_hashers() if hasher().scheme in get_dovecot_schemes()], initial="sha512crypt", help_text=ugettext_lazy("Scheme used to crypt mailbox passwords"), ) rounds_number = forms.IntegerField( label=ugettext_lazy("Rounds"), initial=70000, help_text=ugettext_lazy( "Number of rounds to use (only used by sha256crypt and " "sha512crypt). Must be between 1000 and 999999999, inclusive."), ) update_scheme = YesNoField( label=ugettext_lazy("Update password scheme at login"), initial=True, help_text=ugettext_lazy( "Update user password at login to use the default password scheme") ) default_password = forms.CharField( label=ugettext_lazy("Default password"), initial="password", help_text=ugettext_lazy( "Default password for automatically created accounts.")) random_password_length = forms.IntegerField( label=ugettext_lazy("Random password length"), min_value=8, initial=8, help_text=ugettext_lazy("Length of randomly generated passwords.")) update_password_url = forms.URLField( label=ugettext_lazy("Update password service URL"), initial="", required=False, help_text=ugettext_lazy( "The URL of an external page where users will be able" " to update their password. It applies only to non local" " users, ie. those automatically created after a successful" " external authentication (LDAP, SMTP).")) # LDAP specific settings ldap_sep = SeparatorField(label=ugettext_lazy("LDAP settings")) ldap_server_address = forms.CharField( label=ugettext_lazy("Server address"), initial="localhost", help_text=ugettext_lazy( "The IP address or the DNS name of the LDAP server"), ) ldap_server_port = forms.IntegerField( label=ugettext_lazy("Server port"), initial=389, help_text=ugettext_lazy("The TCP port number used by the LDAP server"), ) ldap_enable_secondary_server = YesNoField( label=ugettext_lazy("Enable secondary server (fallback)"), initial=False, help_text=ugettext_lazy( "Enable a secondary LDAP server which will be used " "if the primary one fails")) ldap_secondary_server_address = forms.CharField( label=ugettext_lazy("Secondary server address"), initial="localhost", help_text=ugettext_lazy( "The IP address or the DNS name of the seondary LDAP server"), ) ldap_secondary_server_port = forms.IntegerField( label=ugettext_lazy("Secondary server port"), initial=389, help_text=ugettext_lazy( "The TCP port number used by the LDAP secondary server"), ) ldap_secured = forms.ChoiceField( label=ugettext_lazy("Use a secured connection"), choices=constants.LDAP_SECURE_MODES, initial="none", help_text=ugettext_lazy( "Use an SSL/STARTTLS connection to access the LDAP server")) ldap_is_active_directory = YesNoField( label=ugettext_lazy("Active Directory"), initial=False, help_text=ugettext_lazy( "Tell if the LDAP server is an Active Directory one")) ldap_admin_groups = forms.CharField( label=ugettext_lazy("Administrator groups"), initial="", help_text=ugettext_lazy( "Members of those LDAP Posix groups will be created as domain " "administrators. Use ';' characters to separate groups."), required=False) ldap_group_type = forms.ChoiceField( label=ugettext_lazy("Group type"), initial="posixgroup", choices=constants.LDAP_GROUP_TYPES, help_text=ugettext_lazy( "The LDAP group type to use with your directory.")) ldap_groups_search_base = forms.CharField( label=ugettext_lazy("Groups search base"), initial="", help_text=ugettext_lazy( "The distinguished name of the search base used to find groups"), required=False) ldap_password_attribute = forms.CharField( label=ugettext_lazy("Password attribute"), initial="userPassword", help_text=ugettext_lazy("The attribute used to store user passwords"), ) # LDAP authentication settings ldap_auth_sep = SeparatorField( label=ugettext_lazy("LDAP authentication settings")) ldap_auth_method = forms.ChoiceField( label=ugettext_lazy("Authentication method"), choices=[("searchbind", ugettext_lazy("Search and bind")), ("directbind", ugettext_lazy("Direct bind"))], initial="searchbind", help_text=ugettext_lazy("Choose the authentication method to use"), ) ldap_bind_dn = forms.CharField( label=ugettext_lazy("Bind DN"), initial="", help_text=ugettext_lazy( "The distinguished name to use when binding to the LDAP server. " "Leave empty for an anonymous bind"), required=False, ) ldap_bind_password = forms.CharField( label=ugettext_lazy("Bind password"), initial="", help_text=ugettext_lazy( "The password to use when binding to the LDAP server " "(with 'Bind DN')"), widget=forms.PasswordInput(render_value=True), required=False) ldap_search_base = forms.CharField( label=ugettext_lazy("Users search base"), initial="", help_text=ugettext_lazy( "The distinguished name of the search base used to find users"), required=False, ) ldap_search_filter = forms.CharField( label=ugettext_lazy("Search filter"), initial="(mail=%(user)s)", help_text=ugettext_lazy( "An optional filter string (e.g. '(objectClass=person)'). " "In order to be valid, it must be enclosed in parentheses."), required=False, ) ldap_user_dn_template = forms.CharField( label=ugettext_lazy("User DN template"), initial="", help_text=ugettext_lazy( "The template used to construct a user's DN. It should contain " "one placeholder (ie. %(user)s)"), required=False, ) # LDAP sync. settings ldap_sync_sep = SeparatorField( label=ugettext_lazy("LDAP synchronization settings")) ldap_sync_bind_dn = forms.CharField( label=ugettext_lazy("Bind DN"), initial="", help_text=ugettext_lazy( "The distinguished name to use when binding to the LDAP server. " "Leave empty for an anonymous bind"), required=False, ) ldap_sync_bind_password = forms.CharField( label=ugettext_lazy("Bind password"), initial="", help_text=ugettext_lazy( "The password to use when binding to the LDAP server " "(with 'Bind DN')"), widget=forms.PasswordInput(render_value=True), required=False) ldap_enable_sync = YesNoField( label=ugettext_lazy("Enable export to LDAP"), initial=False, help_text=ugettext_lazy( "Enable automatic synchronization between local database and " "LDAP directory")) ldap_sync_delete_remote_account = YesNoField( label=ugettext_lazy( "Delete remote LDAP account when local account is deleted"), initial=False, help_text=ugettext_lazy( "Delete remote LDAP account when local account is deleted, " "otherwise it will be disabled.")) ldap_sync_account_dn_template = forms.CharField( label=ugettext_lazy("Account DN template"), initial="", help_text=ugettext_lazy( "The template used to construct an account's DN. It should contain " "one placeholder (ie. %(user)s)"), required=False) ldap_enable_import = YesNoField( label=ugettext_lazy("Enable import from LDAP"), initial=False, help_text=ugettext_lazy( "Enable account synchronization from LDAP directory to local " "database")) ldap_import_search_base = forms.CharField( label=ugettext_lazy("Users search base"), initial="", help_text=ugettext_lazy( "The distinguished name of the search base used to find users"), required=False, ) ldap_import_search_filter = forms.CharField( label=ugettext_lazy("Search filter"), initial="(cn=*)", help_text=ugettext_lazy( "An optional filter string (e.g. '(objectClass=person)'). " "In order to be valid, it must be enclosed in parentheses."), required=False, ) ldap_import_username_attr = forms.CharField( label=ugettext_lazy("Username attribute"), initial="cn", help_text=ugettext_lazy( "The name of the LDAP attribute where the username can be found."), ) dash_sep = SeparatorField(label=ugettext_lazy("Dashboard")) rss_feed_url = forms.URLField( label=ugettext_lazy("Custom RSS feed"), required=False, help_text=ugettext_lazy( "Display custom RSS feed to resellers and domain administrators")) hide_features_widget = YesNoField( label=ugettext_lazy("Hide features widget"), initial=False, help_text=ugettext_lazy( "Hide features widget for resellers and domain administrators")) notif_sep = SeparatorField(label=ugettext_lazy("Notifications")) sender_address = lib_fields.UTF8EmailField( label=_("Sender address"), initial="*****@*****.**", help_text=_("Email address used to send notifications.")) api_sep = SeparatorField(label=ugettext_lazy("Public API")) enable_api_communication = YesNoField( label=ugettext_lazy("Enable communication"), initial=True, help_text=ugettext_lazy( "Enable communication with Modoboa public API")) check_new_versions = YesNoField( label=ugettext_lazy("Check new versions"), initial=True, help_text=ugettext_lazy( "Automatically checks if a newer version is available")) send_new_versions_email = YesNoField( label=ugettext_lazy("Send an email when new versions are found"), initial=False, help_text=ugettext_lazy( "Send an email to notify admins about new versions")) new_versions_email_rcpt = lib_fields.UTF8EmailField( label=_("Recipient"), initial="*****@*****.**", help_text=_("Recipient of new versions notification emails.")) send_statistics = YesNoField(label=ugettext_lazy("Send statistics"), initial=True, help_text=ugettext_lazy( "Send statistics to Modoboa public API " "(counters and used extensions)")) sep3 = SeparatorField(label=ugettext_lazy("Miscellaneous")) inactive_account_threshold = forms.IntegerField( label=_("Inactive account threshold"), initial=30, help_text=_( "An account with a last login date greater than this threshold " "(in days) will be considered as inactive"), ) top_notifications_check_interval = forms.IntegerField( label=_("Top notifications check interval"), initial=30, help_text=_( "Interval between two top notification checks (in seconds)"), ) log_maximum_age = forms.IntegerField( label=ugettext_lazy("Maximum log record age"), initial=365, help_text=ugettext_lazy("The maximum age in days of a log record"), ) items_per_page = forms.IntegerField( label=ugettext_lazy("Items per page"), initial=30, help_text=ugettext_lazy("Number of displayed items per page"), ) default_top_redirection = forms.ChoiceField( label=ugettext_lazy("Default top redirection"), choices=[], initial="user", help_text=ugettext_lazy( "The default redirection used when no application is specified"), ) # Visibility rules visibility_rules = { "ldap_secondary_server_address": "ldap_enable_secondary_server=True", "ldap_secondary_server_port": "ldap_enable_secondary_server=True", "ldap_auth_sep": "authentication_type=ldap", "ldap_auth_method": "authentication_type=ldap", "ldap_bind_dn": "ldap_auth_method=searchbind", "ldap_bind_password": "******", "ldap_search_base": "ldap_auth_method=searchbind", "ldap_search_filter": "ldap_auth_method=searchbind", "ldap_user_dn_template": "ldap_auth_method=directbind", "ldap_admin_groups": "authentication_type=ldap", "ldap_group_type": "authentication_type=ldap", "ldap_groups_search_base": "authentication_type=ldap", "ldap_sync_delete_remote_account": "ldap_enable_sync=True", "ldap_sync_account_dn_template": "ldap_enable_sync=True", "ldap_import_search_base": "ldap_enable_import=True", "ldap_import_search_filter": "ldap_enable_import=True", "ldap_import_username_attr": "ldap_enable_import=True", "check_new_versions": "enable_api_communication=True", "send_statistics": "enable_api_communication=True", "send_new_versions_email": "check_new_versions=True", "new_versions_email_rcpt": "check_new_versions=True" } def __init__(self, *args, **kwargs): super(GeneralParametersForm, self).__init__(*args, **kwargs) self.fields["default_top_redirection"].choices = enabled_applications() def clean_ldap_user_dn_template(self): tpl = self.cleaned_data["ldap_user_dn_template"] try: tpl % {"user": "******"} except (KeyError, ValueError): raise forms.ValidationError(_("Invalid syntax")) return tpl def clean_ldap_sync_account_dn_template(self): tpl = self.cleaned_data["ldap_sync_account_dn_template"] try: tpl % {"user": "******"} except (KeyError, ValueError): raise forms.ValidationError(_("Invalid syntax")) return tpl def clean_rounds_number(self): value = self.cleaned_data["rounds_number"] if value < 1000 or value > 999999999: raise forms.ValidationError(_("Invalid rounds number")) return value def clean_default_password(self): """Check password complexity.""" value = self.cleaned_data["default_password"] password_validation.validate_password(value) return value def clean(self): """Custom validation method Depending on 'ldap_auth_method' value, we check for different required parameters. """ super(GeneralParametersForm, self).clean() cleaned_data = self.cleaned_data if cleaned_data["authentication_type"] != "ldap": return cleaned_data if cleaned_data["ldap_auth_method"] == "searchbind": required_fields = ["ldap_search_base", "ldap_search_filter"] else: required_fields = ["ldap_user_dn_template"] for f in required_fields: if f not in cleaned_data or cleaned_data[f] == u'': self.add_error(f, _("This field is required")) return cleaned_data def _apply_ldap_settings(self, values, backend): """Apply configuration for given backend.""" import ldap from django_auth_ldap.config import (LDAPSearch, PosixGroupType, GroupOfNamesType, ActiveDirectoryGroupType) if not hasattr(settings, backend.setting_fullname("USER_ATTR_MAP")): setattr(settings, backend.setting_fullname("USER_ATTR_MAP"), { "first_name": "givenName", "email": "mail", "last_name": "sn" }) ldap_uri = "ldaps://" if values["ldap_secured"] == "ssl" else "ldap://" ldap_uri += "%s:%s" % (values[backend.srv_address_setting_name], values[backend.srv_port_setting_name]) setattr(settings, backend.setting_fullname("SERVER_URI"), ldap_uri) if values["ldap_secured"] == "starttls": setattr(settings, backend.setting_fullname("START_TLS"), True) if values["ldap_is_active_directory"]: setattr(settings, backend.setting_fullname("GROUP_TYPE"), ActiveDirectoryGroupType()) searchfilter = "(objectClass=group)" elif values["ldap_group_type"] == "groupofnames": setattr(settings, backend.setting_fullname("GROUP_TYPE"), GroupOfNamesType()) searchfilter = "(objectClass=groupOfNames)" else: setattr(settings, backend.setting_fullname("GROUP_TYPE"), PosixGroupType()) searchfilter = "(objectClass=posixGroup)" setattr( settings, backend.setting_fullname("GROUP_SEARCH"), LDAPSearch(values["ldap_groups_search_base"], ldap.SCOPE_SUBTREE, searchfilter)) if values["ldap_auth_method"] == "searchbind": setattr(settings, backend.setting_fullname("BIND_DN"), values["ldap_bind_dn"]) setattr(settings, backend.setting_fullname("BIND_PASSWORD"), values["ldap_bind_password"]) search = LDAPSearch(values["ldap_search_base"], ldap.SCOPE_SUBTREE, values["ldap_search_filter"]) setattr(settings, backend.setting_fullname("USER_SEARCH"), search) else: setattr(settings, backend.setting_fullname("USER_DN_TEMPLATE"), values["ldap_user_dn_template"]) setattr(settings, backend.setting_fullname("BIND_AS_AUTHENTICATING_USER"), True) if values["ldap_is_active_directory"]: setting = backend.setting_fullname("GLOBAL_OPTIONS") if not hasattr(settings, setting): setattr(settings, setting, {ldap.OPT_REFERRALS: False}) else: getattr(settings, setting)[ldap.OPT_REFERRALS] = False def to_django_settings(self): """Apply LDAP related parameters to Django settings. Doing so, we can use the django_auth_ldap module. """ try: import ldap ldap_available = True except ImportError: ldap_available = False values = dict(param_tools.get_global_parameters("core")) if not ldap_available or values["authentication_type"] != "ldap": return from modoboa.lib.authbackends import LDAPBackend self._apply_ldap_settings(values, LDAPBackend) if not values["ldap_enable_secondary_server"]: return from modoboa.lib.authbackends import LDAPSecondaryBackend self._apply_ldap_settings(values, LDAPSecondaryBackend)
class AccountFormMail(forms.Form, DynamicForm): """Form to handle mail part.""" email = lib_fields.UTF8EmailField(label=ugettext_lazy("E-mail"), required=False) quota = forms.IntegerField( label=ugettext_lazy("Quota"), required=False, help_text=_("Quota in MB for this mailbox. Define a custom value or " "use domain's default one. Leave empty to define an " "unlimited value (not allowed for domain " "administrators)."), widget=forms.widgets.TextInput(attrs={"class": "form-control"})) quota_act = forms.BooleanField(required=False) aliases = lib_fields.UTF8AndEmptyUserEmailField( label=ugettext_lazy("Alias(es)"), required=False, help_text=ugettext_lazy( "Alias(es) of this mailbox. Indicate only one address per input, " "press ENTER to add a new input. Use the '*' character to create " "a 'catchall' alias (ex: *@domain.tld).")) def __init__(self, *args, **kwargs): self.mb = kwargs.pop("instance", None) super(AccountFormMail, self).__init__(*args, **kwargs) self.field_widths = {"quota": 3} self.extra_fields = [] result = events.raiseQueryEvent('ExtraFormFields', 'mailform', self.mb) for fname, field in result: self.fields[fname] = field self.extra_fields.append(fname) if self.mb is not None: self.fields["email"].required = True cpt = 1 qset = self.mb.aliasrecipient_set.select_related("alias") for ralias in qset: name = "aliases_%d" % cpt self._create_field(lib_fields.UTF8AndEmptyUserEmailField, name, ralias.alias.address) cpt += 1 self.fields["email"].initial = self.mb.full_address self.fields["quota_act"].initial = self.mb.use_domain_quota if not self.mb.use_domain_quota and self.mb.quota: self.fields["quota"].initial = self.mb.quota else: self.fields["quota_act"].initial = True if len(args) and isinstance(args[0], QueryDict): self._load_from_qdict(args[0], "aliases", lib_fields.UTF8AndEmptyUserEmailField) def clean_email(self): """Ensure lower case emails""" email = self.cleaned_data["email"].lower() self.locpart, domname = split_mailbox(email) if not domname: return email try: self.domain = Domain.objects.get(name=domname) except Domain.DoesNotExist: raise forms.ValidationError(_("Domain does not exist")) if not self.mb: try: core_signals.can_create_object.send(sender=self.__class__, context=self.domain, object_type="mailboxes") except lib_exceptions.ModoboaException as inst: raise forms.ValidationError(inst) return email def clean(self): """Custom fields validation. Check if quota is >= 0 only when the domain value is not used. """ super(AccountFormMail, self).clean() if not self.cleaned_data["quota_act"] \ and self.cleaned_data['quota'] is not None: if self.cleaned_data["quota"] < 0: self.add_error("quota", _("Must be a positive integer")) self.aliases = [] for name, value in self.cleaned_data.iteritems(): if not name.startswith("aliases"): continue if value == "": continue local_part, domname = split_mailbox(value) if not Domain.objects.filter(name=domname).exists(): self.add_error(name, _("Local domain does not exist")) break self.aliases.append(value.lower()) return self.cleaned_data def create_mailbox(self, user, account): """Create a mailbox associated to :kw:`account`.""" if not user.can_access(self.domain): raise lib_exceptions.PermDeniedException core_signals.can_create_object.send(self.__class__, context=user, object_type="mailboxes") self.mb = Mailbox(address=self.locpart, domain=self.domain, user=account, use_domain_quota=self.cleaned_data["quota_act"]) self.mb.set_quota(self.cleaned_data["quota"], user.has_perm("admin.add_domain")) self.mb.save(creator=user) def _update_aliases(self, user, account): """Update mailbox aliases.""" qset = self.mb.aliasrecipient_set.select_related("alias").filter( alias__internal=False) for ralias in qset: if ralias.alias.address not in self.aliases: alias = ralias.alias ralias.delete() if alias.recipients_count > 0: continue alias.delete() else: self.aliases.remove(ralias.alias.address) if not self.aliases: return core_signals.can_create_object.send(self.__class__, context=user, object_type="mailbox_aliases", count=len(self.aliases)) core_signals.can_create_object.send(self.__class__, context=self.mb.domain, object_type="mailbox_aliases", count=len(self.aliases)) for alias in self.aliases: if self.mb.aliasrecipient_set.select_related("alias").filter( alias__address=alias).exists(): continue local_part, domname = split_mailbox(alias) al = Alias(address=alias, enabled=account.is_active) al.domain = Domain.objects.get(name=domname) al.save() al.set_recipients([self.mb.full_address]) al.post_create(user) def save(self, user, account): """Save or update account mailbox.""" if self.cleaned_data["email"] == "": return None if self.cleaned_data["quota_act"]: self.cleaned_data["quota"] = None if not hasattr(self, "mb") or self.mb is None: self.create_mailbox(user, account) else: self.cleaned_data["use_domain_quota"] = ( self.cleaned_data["quota_act"]) self.mb.update_from_dict(user, self.cleaned_data) events.raiseEvent('SaveExtraFormFields', 'mailform', self.mb, self.cleaned_data) account.email = self.cleaned_data["email"] account.save() self._update_aliases(user, account) return self.mb