WIP: new system to manage settings.

This commit is contained in:
Antoine Nguyen
2018-10-05 14:06:59 +02:00
parent d0ae7a06e5
commit af1461469a
12 changed files with 376 additions and 21 deletions
+13
View File
@@ -16,3 +16,16 @@ export const createDomain = data => {
export const updateDomain = data => {
return domainResource.update({ pk: data.pk }, data)
}
var customParametersActions = {
structure: {method: 'GET', url: '/api/v1/parameters/structure/'}
}
var parametersResource = Vue.resource('/api/v1/parameters/', {}, customParametersActions)
export const getParameters = () => {
return parametersResource.get()
}
export const getParametersStructure = () => {
return parametersResource.structure()
}
@@ -59,9 +59,9 @@ export default {
label: 'Logs'
},
{
route: 'DomainList',
route: 'ParametersEdit',
icon: 'tune',
label: 'Settings'
label: 'Parameters'
}
]
}
@@ -0,0 +1,46 @@
<template>
<div>
<h1 class="title">Parameters</h1>
<form>
<h2 class="subtitle" v-for="(element, index) in structure"
v-if="element.type === 'section'" :key="index">
{{ element.label }}
</h2>
<b-field v-else :label="element.label" horizontal>
<b-input v-if="element.widget === 'CharField' || element.widget === 'IntegerField'"
v-model="parameters[element.name]">
</b-input>
<b-checkbox v-else-if="element.widget === 'BooleanField'"
v-model="parameters[element.name]"></b-checkbox>
<b-select v-else-if="element.widget === 'ChoiceField'"
v-model="parameters[element.name]">
<option v-for="choice in Object.entries(element.choices)"
:value="choice[0]" :key="choice[0]">
{{ choice[1] }}
</option>
</b-select>
</b-field>
</form>
</div>
</template>
<script>
import * as api from '@/api'
export default {
data () {
return {
structure: [],
parameters: {}
}
},
created () {
api.getParametersStructure().then(response => {
this.structure = response.data
})
api.getParameters().then(response => {
this.parameters = response.data
})
}
}
</script>
+23 -17
View File
@@ -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
}
]
})
+179
View File
@@ -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"
)
}
}
}
}
+5 -2
View File
@@ -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):
+5
View File
@@ -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": [
+16
View File
@@ -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
+52
View File
@@ -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:
+11
View File
@@ -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
+23
View File
@@ -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)
+1
View File
@@ -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")),
]