use jsonschema to validate config and massive refactor
This commit is contained in:
330
pymodmilter/config.py
Normal file
330
pymodmilter/config.py
Normal file
@@ -0,0 +1,330 @@
|
||||
# PyMod-Milter is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# PyMod-Milter is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with PyMod-Milter. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
__all__ = [
|
||||
"BaseConfig",
|
||||
"ConditionsConfig",
|
||||
"AddHeaderConfig",
|
||||
"ModHeaderConfig",
|
||||
"DelHeaderConfig",
|
||||
"AddDisclaimerConfig",
|
||||
"RewriteLinksConfig",
|
||||
"StoreConfig",
|
||||
"NotifyConfig",
|
||||
"WhitelistConfig",
|
||||
"QuarantineConfig",
|
||||
"ActionConfig",
|
||||
"RuleConfig",
|
||||
"MilterConfig"]
|
||||
|
||||
import jsonschema
|
||||
import logging
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"additionalProperties": True,
|
||||
"properties": {
|
||||
"loglevel": {"type": "string", "default": "info"}}}
|
||||
|
||||
def __init__(self, config):
|
||||
required = self.JSON_SCHEMA["required"]
|
||||
properties = self.JSON_SCHEMA["properties"]
|
||||
for p in properties.keys():
|
||||
if p in required:
|
||||
continue
|
||||
elif p not in config and "default" in properties[p]:
|
||||
config[p] = properties[p]["default"]
|
||||
try:
|
||||
jsonschema.validate(config, self.JSON_SCHEMA)
|
||||
except jsonschema.exceptions.ValidationError as e:
|
||||
raise RuntimeError(e)
|
||||
|
||||
self._config = config
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._config[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._config[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._config[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self._config
|
||||
|
||||
def keys(self):
|
||||
return self._config.keys()
|
||||
|
||||
def items(self):
|
||||
return self._config.items()
|
||||
|
||||
def get_loglevel(self, debug):
|
||||
if debug:
|
||||
level = logging.DEBUG
|
||||
else:
|
||||
level = getattr(logging, self["loglevel"].upper(), None)
|
||||
assert isinstance(level, int), \
|
||||
"loglevel: invalid value"
|
||||
return level
|
||||
|
||||
def get_config(self):
|
||||
return self._config
|
||||
|
||||
class WhitelistConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["type"],
|
||||
"additionalProperties": True,
|
||||
"properties": {
|
||||
"type": {"enum": ["db"]}},
|
||||
"if": {"properties": {"type": {"const": "db"}}},
|
||||
"then": {
|
||||
"required": ["connection", "table"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"type": {"type": "string"},
|
||||
"connection": {"type": "string"},
|
||||
"table": {"type": "string"}}}}
|
||||
|
||||
|
||||
class ConditionsConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"metavar": {"type": "string"},
|
||||
"local": {"type": "boolean"},
|
||||
"hosts": {"type": "array",
|
||||
"items": {"type": "string"}},
|
||||
"envfrom": {"type": "string"},
|
||||
"envto": {"type": "string"},
|
||||
"header": {"type": "string"},
|
||||
"var": {"type": "string"},
|
||||
"whitelist": {"type": "object"}}}
|
||||
|
||||
def __init__(self, config, rec=True):
|
||||
super().__init__(config)
|
||||
if rec:
|
||||
if "whitelist" in self:
|
||||
self["whitelist"] = WhitelistConfig(self["whitelist"])
|
||||
|
||||
|
||||
class AddHeaderConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["field", "value"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"field": {"type": "string"},
|
||||
"value": {"type": "string"}}}
|
||||
|
||||
|
||||
class ModHeaderConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["field", "value"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"field": {"type": "string"},
|
||||
"value": {"type": "string"},
|
||||
"search": {"type": "string"}}}
|
||||
|
||||
|
||||
class DelHeaderConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["field"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"field": {"type": "string"},
|
||||
"value": {"type": "string"}}}
|
||||
|
||||
|
||||
class AddDisclaimerConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["action", "html_template", "text_template"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"action": {"type": "string"},
|
||||
"html_template": {"type": "string"},
|
||||
"text_template": {"type": "string"},
|
||||
"error_policy": {"type": "string"}}}
|
||||
|
||||
|
||||
class RewriteLinksConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["repl"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"repl": {"type": "string"}}}
|
||||
|
||||
|
||||
class StoreConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["type"],
|
||||
"additionalProperties": True,
|
||||
"properties": {
|
||||
"type": {"enum": ["file"]}},
|
||||
"if": {"properties": {"type": {"const": "file"}}},
|
||||
"then": {
|
||||
"required": ["directory"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"type": {"type": "string"},
|
||||
"directory": {"type": "string"},
|
||||
"metavar": {"type": "string"},
|
||||
"original": {"type": "boolean", "default": True}}}}
|
||||
|
||||
|
||||
class NotifyConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["type"],
|
||||
"additionalProperties": True,
|
||||
"properties": {
|
||||
"type": {"enum": ["email"]}},
|
||||
"if": {"properties": {"type": {"const": "email"}}},
|
||||
"then": {
|
||||
"required": ["smtp_host", "smtp_port", "envelope_from",
|
||||
"from_header", "subject", "template"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"type": {"type": "string"},
|
||||
"smtp_host": {"type": "string"},
|
||||
"smtp_port": {"type": "number"},
|
||||
"envelope_from": {"type": "string"},
|
||||
"from_header": {"type": "string"},
|
||||
"subject": {"type": "string"},
|
||||
"template": {"type": "string"},
|
||||
"repl_img": {"type": "string"},
|
||||
"embed_imgs": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": True}}}}
|
||||
|
||||
|
||||
class QuarantineConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["store"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"notify": {"type": "object"},
|
||||
"milter_action": {"type": "string"},
|
||||
"reject_reason": {"type": "string"},
|
||||
"whitelist": {"type": "object"},
|
||||
"store": {"type": "object"}}}
|
||||
|
||||
def __init__(self, config, rec=True):
|
||||
super().__init__(config)
|
||||
if rec:
|
||||
self["store"] = StoreConfig(self["store"])
|
||||
if "notify" in self:
|
||||
self["notify"] = NotifyConfig(self["notify"])
|
||||
if "whitelist" in self:
|
||||
self["whitelist"] = ConditionsConfig(
|
||||
{"whitelist": self["whitelist"]}, rec)
|
||||
|
||||
|
||||
class ActionConfig(BaseConfig):
|
||||
ACTION_TYPES = {
|
||||
"add_header": AddHeaderConfig,
|
||||
"mod_header": ModHeaderConfig,
|
||||
"del_header": DelHeaderConfig,
|
||||
"add_disclaimer": AddDisclaimerConfig,
|
||||
"rewrite_links": RewriteLinksConfig,
|
||||
"store": StoreConfig,
|
||||
"notify": NotifyConfig,
|
||||
"quarantine": QuarantineConfig}
|
||||
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["type", "args"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "default": "action"},
|
||||
"loglevel": {"type": "string", "default": "info"},
|
||||
"pretend": {"type": "boolean", "default": False},
|
||||
"conditions": {"type": "object"},
|
||||
"type": {"enum": list(ACTION_TYPES.keys())},
|
||||
"args": {"type": "object"}}}
|
||||
|
||||
def __init__(self, config, rec=True):
|
||||
super().__init__(config)
|
||||
if rec:
|
||||
if "conditions" in self:
|
||||
self["conditions"] = ConditionsConfig(self["conditions"])
|
||||
self["action"] = self.ACTION_TYPES[self["type"]](self["args"])
|
||||
|
||||
|
||||
class RuleConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["actions"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"name": {"type": "string", "default": "rule"},
|
||||
"loglevel": {"type": "string", "default": "info"},
|
||||
"pretend": {"type": "boolean", "default": False},
|
||||
"conditions": {"type": "object"},
|
||||
"actions": {"type": "array"}}}
|
||||
|
||||
def __init__(self, config, rec=True):
|
||||
super().__init__(config)
|
||||
if rec:
|
||||
if "conditions" in self:
|
||||
self["conditions"] = ConditionsConfig(self["conditions"])
|
||||
|
||||
actions = []
|
||||
for idx, action in enumerate(self["actions"]):
|
||||
actions.append(ActionConfig(action, rec))
|
||||
self["actions"] = actions
|
||||
|
||||
|
||||
class MilterConfig(BaseConfig):
|
||||
JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"required": ["rules"],
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"socket": {"type": "string"},
|
||||
"local_addrs": {"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"default": [
|
||||
"fe80::/64",
|
||||
"::1/128",
|
||||
"127.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16"]},
|
||||
"loglevel": {"type": "string", "default": "info"},
|
||||
"pretend": {"type": "boolean", "default": False},
|
||||
"rules": {"type": "array"}}}
|
||||
|
||||
def __init__(self, config, rec=True):
|
||||
super().__init__(config)
|
||||
if rec:
|
||||
rules = []
|
||||
for idx, rule in enumerate(self["rules"]):
|
||||
rules.append(RuleConfig(rule, rec))
|
||||
self["rules"] = rules
|
||||
Reference in New Issue
Block a user