+
+
Parameters
+
+
+
+
+
diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js
index 27fcb93c6..618dd0931 100644
--- a/frontend/src/router/index.js
+++ b/frontend/src/router/index.js
@@ -2,25 +2,31 @@ import Vue from 'vue'
import Router from 'vue-router'
import DomainList from '@/components/domains/DomainList'
import DomainForm from '@/components/domains/DomainForm'
+import ParametersForm from '@/components/parameters/ParametersForm'
Vue.use(Router)
export default new Router({
- routes: [
- {
- path: '/domains/',
- name: 'DomainList',
- component: DomainList
- },
- {
- path: '/domains/add/',
- name: 'DomainAdd',
- component: DomainForm
- },
- {
- path: '/domains/:domainPk([0-9]+)/',
- name: 'DomainEdit',
- component: DomainForm
- }
- ]
+ routes: [
+ {
+ path: '/domains/',
+ name: 'DomainList',
+ component: DomainList
+ },
+ {
+ path: '/domains/add/',
+ name: 'DomainAdd',
+ component: DomainForm
+ },
+ {
+ path: '/domains/:domainPk([0-9]+)/',
+ name: 'DomainEdit',
+ component: DomainForm
+ },
+ {
+ path: '/parameters/',
+ name: 'ParametersEdit',
+ component: ParametersForm
+ }
+ ]
})
diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py
index c0af12596..4eafc5716 100644
--- a/modoboa/core/app_settings.py
+++ b/modoboa/core/app_settings.py
@@ -7,6 +7,8 @@ from django.conf import settings
from django.contrib.auth import password_validation
from django.utils.translation import ugettext as _, ugettext_lazy
+from rest_framework import serializers
+
from modoboa.lib import fields as lib_fields
from modoboa.lib.form_utils import (
HorizontalRadioSelect, SeparatorField, YesNoField
@@ -441,3 +443,180 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
})
else:
settings.AUTH_LDAP_GLOBAL_OPTIONS[ldap.OPT_REFERRALS] = False
+
+
+GLOBAL_PARAMETERS = {
+ "authentication": {
+ "label": ugettext_lazy("Authentication"),
+ "params": {
+ "authentication_type": {
+ "field": serializers.ChoiceField(
+ choices=[("local", ugettext_lazy("Local")),
+ ("ldap", "LDAP")],
+ default="local"
+ ),
+ "label": ugettext_lazy("Authentication type"),
+ "help_text": ugettext_lazy(
+ "The backend used for authentication"),
+ },
+ "password_scheme": {
+ "label": ugettext_lazy("Default password scheme"),
+ "field": serializers.ChoiceField(
+ 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)"))],
+ default="sha512crypt"
+ ),
+ "help_text": ugettext_lazy(
+ "Scheme used to crypt mailbox passwords"),
+ },
+ "rounds_number": {
+ "label": ugettext_lazy("Rounds"),
+ "field": serializers.IntegerField(default=70000),
+ "help_text": ugettext_lazy(
+ "Number of rounds to use (only used by sha256crypt and "
+ "sha512crypt). Must be between 1000 and 999999999, "
+ "inclusive."
+ )
+ },
+ "default_password": {
+ "label": ugettext_lazy("Default password"),
+ "field": serializers.CharField(default="password"),
+ "help_text": ugettext_lazy(
+ "Default password for automatically created accounts.")
+ },
+ "random_password_length": {
+ "label": ugettext_lazy("Random password length"),
+ "field": serializers.IntegerField(min_value=8, default=8),
+ "help_text": ugettext_lazy(
+ "Length of randomly generated passwords.")
+ }
+ }
+ },
+ "ldap": {
+ "label": _("LDAP"),
+ "display": "authentication_type=ldap",
+ "params": {
+ "ldap_server_address": {
+ "label": ugettext_lazy("Server address"),
+ "field": serializers.CharField(default="localhost"),
+ "help_text": ugettext_lazy(
+ "The IP address or the DNS name of the LDAP server"),
+ },
+ "ldap_server_port": {
+ "label": ugettext_lazy("Server port"),
+ "field": serializers.IntegerField(default=389),
+ "help_text": ugettext_lazy(
+ "The TCP port number used by the LDAP server")
+ },
+ "ldap_secured": {
+ "label": ugettext_lazy("Use a secured connection"),
+ "field": serializers.ChoiceField(
+ choices=constants.LDAP_SECURE_MODES,
+ default="none"
+ ),
+ "help_text": ugettext_lazy(
+ "Use an SSL/STARTTLS connection to access the LDAP server")
+ },
+ "ldap_auth_method": {
+ "label": ugettext_lazy("Authentication method"),
+ "field": serializers.ChoiceField(
+ choices=constants.LDAP_AUTH_METHODS,
+ default="searchbind",
+ ),
+ "help_text": ugettext_lazy(
+ "Choose the authentication method to use"),
+ },
+ "ldap_bind_dn": {
+ "label": ugettext_lazy("Bind DN"),
+ "field": serializers.CharField(default="", required=False),
+ "help_text": ugettext_lazy(
+ "The distinguished name to use when binding to the LDAP "
+ "server. Leave empty for an anonymous bind"
+ ),
+ "display": "ldap_auth_method=searchbind"
+ },
+ "ldap_bind_password": {
+ "label": ugettext_lazy("Bind password"),
+ "field": serializers.CharField(default="", required=False),
+ "help_text": ugettext_lazy(
+ "The password to use when binding to the LDAP server "
+ "(with 'Bind DN')"
+ ),
+ "display": "ldap_auth_method=searchbind"
+ },
+ "ldap_search_base": {
+ "label": ugettext_lazy("Users search base"),
+ "field": serializers.CharField(default="", required=False),
+ "help_text": ugettext_lazy(
+ "The distinguished name of the search base used to find "
+ "users"
+ ),
+ "display": "ldap_auth_method=searchbind"
+ },
+ "ldap_search_filter": {
+ "label": ugettext_lazy("Search filter"),
+ "field": serializers.CharField(
+ default="(mail=%(user)s)", required=False),
+ "help_text": ugettext_lazy(
+ "An optional filter string (e.g. '(objectClass=person)'). "
+ "In order to be valid, it must be enclosed in parentheses."
+ ),
+ "display": "ldap_auth_method=searchbind"
+ },
+ "ldap_user_dn_template": {
+ "label": ugettext_lazy("User DN template"),
+ "field": serializers.CharField(default="", required=False),
+ "help_text": ugettext_lazy(
+ "The template used to construct a user's DN. It should "
+ "contain one placeholder (ie. %(user)s)"
+ ),
+ "display": "ldap_auth_method=directbind"
+ },
+ "ldap_password_attribute": {
+ "label": ugettext_lazy("Password attribute"),
+ "field": serializers.CharField(default="userPassword"),
+ "help_text": ugettext_lazy(
+ "The attribute used to store user passwords"),
+ },
+ "ldap_is_active_directory": {
+ "label": ugettext_lazy("Active Directory"),
+ "field": serializers.BooleanField(default=False),
+ "help_text": ugettext_lazy(
+ "Tell if the LDAP server is an Active Directory one")
+ },
+ "ldap_admin_groups": {
+ "label": ugettext_lazy("Administrator groups"),
+ "field": serializers.CharField(default="", required=False),
+ "help_text": ugettext_lazy(
+ "Members of those LDAP Posix groups will be created as "
+ "domain administrators. Use ';' characters to separate "
+ "groups."
+ )
+ },
+ "ldap_group_type": {
+ "label": ugettext_lazy("Group type"),
+ "field": serializers.ChoiceField(
+ default="posixgroup",
+ choices=constants.LDAP_GROUP_TYPES
+ ),
+ "help_text": ugettext_lazy(
+ "The LDAP group type to use with your directory."
+ )
+ },
+ "ldap_groups_search_base": {
+ "label": ugettext_lazy("Groups search base"),
+ "field": serializers.CharField(default="", required=False),
+ "help_text": ugettext_lazy(
+ "The distinguished name of the search base used to find "
+ "groups"
+ )
+ }
+ }
+ }
+}
diff --git a/modoboa/core/apps.py b/modoboa/core/apps.py
index 3e4fa2859..2e1b1b17b 100644
--- a/modoboa/core/apps.py
+++ b/modoboa/core/apps.py
@@ -16,10 +16,13 @@ def load_core_settings():
order to load base settings.
"""
from modoboa.parameters import tools as param_tools
- from .app_settings import GeneralParametersForm
+ from . import app_settings
param_tools.registry.add(
- "global", GeneralParametersForm, ugettext_lazy("General"))
+ "global", app_settings.GeneralParametersForm, ugettext_lazy("General"))
+ param_tools.registry.add2(
+ "global", "core", ugettext_lazy("General"),
+ app_settings.GLOBAL_PARAMETERS)
class CoreConfig(AppConfig):
diff --git a/modoboa/core/constants.py b/modoboa/core/constants.py
index 997198672..03db30206 100644
--- a/modoboa/core/constants.py
+++ b/modoboa/core/constants.py
@@ -55,6 +55,11 @@ LDAP_SECURE_MODES = [
("ssl", "SSL/TLS")
]
+LDAP_AUTH_METHODS = [
+ ("searchbind", ugettext_lazy("Search and bind")),
+ ("directbind", ugettext_lazy("Direct bind"))
+]
+
PERMISSIONS = {
"SimpleUsers": [],
"DomainAdmins": [
diff --git a/modoboa/parameters/serializers.py b/modoboa/parameters/serializers.py
new file mode 100644
index 000000000..42d5f4514
--- /dev/null
+++ b/modoboa/parameters/serializers.py
@@ -0,0 +1,16 @@
+"""Parameters serializers."""
+
+from rest_framework import serializers
+
+from . import tools
+
+
+class ParametersSerializer(serializers.Serializer):
+ """Parameters serializer."""
+
+ def __init__(self, *args, **kwargs):
+ """Generate serializer."""
+ super(ParametersSerializer, self).__init__(*args, **kwargs)
+ for name, field in tools.registry.get_fields("global"):
+ field.source = None
+ self.fields[name] = field
diff --git a/modoboa/parameters/tools.py b/modoboa/parameters/tools.py
index 5187ec0a5..5b0ac2983 100644
--- a/modoboa/parameters/tools.py
+++ b/modoboa/parameters/tools.py
@@ -35,6 +35,10 @@ class Registry(object):
"global": {},
"user": {}
}
+ self._registry2 = {
+ "global": {},
+ "user": {}
+ }
def add(self, level, formclass, label):
"""Add a new class containing parameters."""
@@ -42,6 +46,12 @@ class Registry(object):
"label": label, "formclass": formclass, "defaults": {}
}
+ def add2(self, level, app, label, params):
+ """Add new parameters for given app and level."""
+ self._registry2[level][app] = {
+ "label": label, "params": params
+ }
+
def _load_default_values(self, level):
"""Load default values."""
for _app, data in list(self._registry[level].items()):
@@ -76,6 +86,39 @@ class Registry(object):
})
return result
+ def get_fields(self, level, *args, **kwargs):
+ """Return fields to include in serializer."""
+ result = []
+ for app, fields in list(self._registry2[level].items()):
+ for section, sconfig in list(fields["params"].items()):
+ for name, config in list(sconfig["params"].items()):
+ result.append(("{}-{}".format(app, name), config["field"]))
+ return result
+
+ def get_structure(self, level):
+ """Return fields structure."""
+ result = []
+ for app, fields in list(self._registry2[level].items()):
+ for section, sconfig in list(fields["params"].items()):
+ result.append({
+ "type": "section",
+ "label": sconfig["label"],
+ "display": sconfig.get("display", "")
+ })
+ for name, config in list(sconfig["params"].items()):
+ data = {
+ "type": "field",
+ "name": "{}-{}".format(app, name),
+ "label": config["label"],
+ "help_text": config.get("help_text", ""),
+ "display": config.get("display", ""),
+ "widget": config["field"].__class__.__name__,
+ }
+ if data["widget"] == "ChoiceField":
+ data["choices"] = config["field"].choices
+ result.append(data)
+ return result
+
def exists(self, level, app, parameter=None):
"""Check if parameter exists."""
parameters = self._registry[level]
@@ -137,6 +180,15 @@ class Manager(object):
value = self._parameters[app].get(parameter, value)
yield (parameter, value)
+ def get_values_dict(self, app=None):
+ """Return all values for the given app as a dictionary."""
+ if app is None:
+ app = guess_extension_name()
+ values = registry.get_defaults(self._level, app)
+ return {
+ parameter: value for parameter, value in list(values.items())
+ }
+
def set_value(self, parameter, value, app=None):
"""Set parameter for the given app."""
if app is None:
diff --git a/modoboa/parameters/urls_api.py b/modoboa/parameters/urls_api.py
new file mode 100644
index 000000000..e9cbeabb3
--- /dev/null
+++ b/modoboa/parameters/urls_api.py
@@ -0,0 +1,11 @@
+"""Parameters API urls."""
+
+from rest_framework import routers
+
+from . import viewsets
+
+
+router = routers.SimpleRouter()
+router.register(
+ r"parameters", viewsets.ParametersViewSet, base_name="parameter")
+urlpatterns = router.urls
diff --git a/modoboa/parameters/viewsets.py b/modoboa/parameters/viewsets.py
new file mode 100644
index 000000000..ae90feed9
--- /dev/null
+++ b/modoboa/parameters/viewsets.py
@@ -0,0 +1,23 @@
+"""Parameters viewsets."""
+
+from rest_framework import decorators, response, viewsets
+
+from . import serializers
+from . import tools
+
+
+class ParametersViewSet(viewsets.ViewSet):
+ """Parameter viewset."""
+
+ @decorators.list_route(methods=["get"])
+ def structure(self, request):
+ """Return parameter schema."""
+ data = tools.registry.get_structure("global")
+ return response.Response(data)
+
+ def list(self, request):
+ """Return all parameters."""
+ parameters = request.localconfig.parameters.get_values_dict("core")
+ serializer = serializers.ParametersSerializer(data=parameters)
+ serializer.is_valid(raise_exception=True)
+ return response.Response(serializer.validated_data)
diff --git a/modoboa/urls_api.py b/modoboa/urls_api.py
index ea1770cda..ca24ad5da 100644
--- a/modoboa/urls_api.py
+++ b/modoboa/urls_api.py
@@ -10,6 +10,7 @@ from modoboa.core.extensions import exts_pool
urlpatterns = [
url("", include("modoboa.admin.urls_api")),
+ url("", include("modoboa.parameters.urls_api")),
url("", include("modoboa.limits.urls_api")),
url("", include("modoboa.relaydomains.urls_api")),
]