Development
This commit is contained in:
parent
270856b391
commit
2bfa5ff29a
18 changed files with 814 additions and 565 deletions
|
|
@ -2,7 +2,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_banned_ips', __name__)
|
||||
|
||||
|
|
@ -53,6 +53,11 @@ def add_banned_ip():
|
|||
'ip': ip,
|
||||
'enabled': True,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -77,6 +82,11 @@ def toggle_banned_ip():
|
|||
return redirect(VIEW)
|
||||
|
||||
items[idx]['enabled'] = not items[idx].get('enabled', True)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -107,6 +117,11 @@ def edit_banned_ip():
|
|||
return redirect(VIEW)
|
||||
|
||||
items[idx].update({'description': description, 'ip': ip, 'enabled': enabled})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -131,6 +146,11 @@ def delete_banned_ip():
|
|||
return redirect(VIEW)
|
||||
|
||||
removed = items.pop(idx)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from auth import require_level
|
|||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import re
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_blocklists', __name__)
|
||||
|
||||
|
|
@ -76,6 +76,11 @@ def add_blocklist():
|
|||
'url': fields['url'],
|
||||
'save_as': _save_as_from_name(fields['name']),
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -110,6 +115,11 @@ def edit_blocklist():
|
|||
'format': fields['format'],
|
||||
'url': fields['url'],
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -134,6 +144,11 @@ def delete_blocklist():
|
|||
return redirect(VIEW)
|
||||
|
||||
removed = items.pop(idx)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
import json
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_ddns_providers', __name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import ipaddress
|
||||
|
||||
from flask import Blueprint, request, redirect, flash
|
||||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_dhcp_reservations', __name__)
|
||||
|
||||
|
|
@ -46,12 +48,30 @@ def _parse_ip():
|
|||
return ip
|
||||
|
||||
|
||||
def _check_ip_conflicts(ip, vlan):
|
||||
"""Return an error message if ip conflicts with pool range or server identities, else None."""
|
||||
dhcp = vlan.get('dhcp_information', {})
|
||||
pool_start = dhcp.get('dynamic_pool_start')
|
||||
pool_end = dhcp.get('dynamic_pool_end')
|
||||
if pool_start and pool_end:
|
||||
try:
|
||||
if (ipaddress.IPv4Address(pool_start) <= ipaddress.IPv4Address(ip)
|
||||
<= ipaddress.IPv4Address(pool_end)):
|
||||
return f'{ip} falls within the dynamic pool range ({pool_start}–{pool_end}).'
|
||||
except Exception:
|
||||
pass
|
||||
identity_ips = {s['ip'] for s in vlan.get('server_identities', []) if s.get('ip')}
|
||||
if ip in identity_ips:
|
||||
return f'{ip} is already assigned as a server identity IP.'
|
||||
return None
|
||||
|
||||
|
||||
@bp.route('/action/add_dhcp_reservation', methods=['POST'])
|
||||
@require_level('administrator')
|
||||
def add_dhcp_reservation():
|
||||
vlan_name = sanitize.name(request.form.get('vlan_name', ''))
|
||||
description = sanitize.text(request.form.get('description', ''))
|
||||
hostname = sanitize.domainname(request.form.get('hostname', ''))
|
||||
hostname = validate.domainname(request.form.get('hostname', ''))
|
||||
mac = sanitize.mac(request.form.get('mac', ''))
|
||||
ip = _parse_ip()
|
||||
radius_client = 'radius_client' in request.form
|
||||
|
|
@ -76,6 +96,11 @@ def add_dhcp_reservation():
|
|||
flash(f'The configuration has not been saved because VLAN "{vlan_name}" was not found.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
conflict = _check_ip_conflicts(ip, vlan)
|
||||
if conflict:
|
||||
flash(f'The configuration has not been saved because {conflict}', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
vlan.setdefault('reservations', []).append({
|
||||
'description': description,
|
||||
'hostname': hostname,
|
||||
|
|
@ -84,6 +109,11 @@ def add_dhcp_reservation():
|
|||
'radius_client': radius_client,
|
||||
'enabled': True,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -110,6 +140,11 @@ def toggle_dhcp_reservation():
|
|||
|
||||
res = vlans[vi]['reservations'][ri]
|
||||
res['enabled'] = not res.get('enabled', True)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -125,7 +160,7 @@ def edit_dhcp_reservation():
|
|||
return redirect(VIEW)
|
||||
|
||||
description = sanitize.text(request.form.get('description', ''))
|
||||
hostname = sanitize.domainname(request.form.get('hostname', ''))
|
||||
hostname = validate.domainname(request.form.get('hostname', ''))
|
||||
mac = sanitize.mac(request.form.get('mac', ''))
|
||||
ip = _parse_ip()
|
||||
radius_client = 'radius_client' in request.form
|
||||
|
|
@ -146,6 +181,11 @@ def edit_dhcp_reservation():
|
|||
flash('Entry not found.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
conflict = _check_ip_conflicts(ip, vlans[vi])
|
||||
if conflict:
|
||||
flash(f'The configuration has not been saved because {conflict}', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
res = vlans[vi]['reservations'][ri]
|
||||
res.update({
|
||||
'description': description,
|
||||
|
|
@ -155,6 +195,11 @@ def edit_dhcp_reservation():
|
|||
'radius_client': radius_client,
|
||||
'enabled': 'enabled' in request.form,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -180,6 +225,11 @@ def delete_dhcp_reservation():
|
|||
return redirect(VIEW)
|
||||
|
||||
removed = vlans[vi]['reservations'].pop(ri)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_general', __name__)
|
||||
|
||||
|
|
@ -14,11 +15,8 @@ def apply_general():
|
|||
dnsmasq_log_queries = 'dnsmasq_log_queries' in request.form
|
||||
daily_execute_time = sanitize.time_24h(request.form.get('daily_execute_time_24hr_local', ''))
|
||||
|
||||
try:
|
||||
log_max_kb = int(log_max_kb_raw)
|
||||
if log_max_kb < 64:
|
||||
raise ValueError
|
||||
except (ValueError, TypeError):
|
||||
log_max_kb = validate.int_range(log_max_kb_raw, 64, None)
|
||||
if log_max_kb is None:
|
||||
flash('Max Log Size must be a number >= 64.', 'error')
|
||||
return redirect('/view/view_general')
|
||||
|
||||
|
|
@ -33,6 +31,11 @@ def apply_general():
|
|||
'dnsmasq_log_queries': dnsmasq_log_queries,
|
||||
'daily_execute_time_24hr_local': daily_execute_time,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect('/view/view_general')
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_host_overrides', __name__)
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ def _hash_ok():
|
|||
@require_level('administrator')
|
||||
def add_host_override():
|
||||
description = sanitize.text(request.form.get('description', ''))
|
||||
host = sanitize.domainname(request.form.get('host', ''))
|
||||
host = validate.domainname(request.form.get('host', ''))
|
||||
ip = sanitize.ip(request.form.get('ip', ''))
|
||||
|
||||
if not host or not ip:
|
||||
|
|
@ -72,6 +73,11 @@ def add_host_override():
|
|||
'ip': ip,
|
||||
'enabled': True,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -96,6 +102,11 @@ def toggle_host_override():
|
|||
return redirect(VIEW)
|
||||
|
||||
items[idx]['enabled'] = not items[idx].get('enabled', True)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -111,7 +122,7 @@ def edit_host_override():
|
|||
return redirect(VIEW)
|
||||
|
||||
description = sanitize.text(request.form.get('description', ''))
|
||||
host = sanitize.domainname(request.form.get('host', ''))
|
||||
host = validate.domainname(request.form.get('host', ''))
|
||||
ip = sanitize.ip(request.form.get('ip', ''))
|
||||
enabled = request.form.get('enabled') == 'on'
|
||||
|
||||
|
|
@ -133,6 +144,11 @@ def edit_host_override():
|
|||
return redirect(VIEW)
|
||||
|
||||
items[idx].update({'description': description, 'host': host, 'ip': ip, 'enabled': enabled})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -157,6 +173,11 @@ def delete_host_override():
|
|||
return redirect(VIEW)
|
||||
|
||||
removed = items.pop(idx)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import verify_core_hash, queued_msg, queue_command
|
||||
import sanitize
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_iface_config', __name__)
|
||||
|
||||
|
|
@ -47,11 +48,8 @@ def apply_iface_config():
|
|||
|
||||
mtu_int = None
|
||||
if mtu:
|
||||
try:
|
||||
mtu_int = int(mtu)
|
||||
if not (68 <= mtu_int <= 9000):
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
mtu_int = validate.int_range(mtu, 68, 9000)
|
||||
if mtu_int is None:
|
||||
flash('MTU must be an integer between 68 and 9000.', 'error')
|
||||
return redirect(_VIEW)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_inter_vlan', __name__)
|
||||
|
||||
|
|
@ -83,6 +83,11 @@ def add_inter_vlan():
|
|||
|
||||
core = load_core()
|
||||
core.setdefault('inter_vlan_exceptions', []).append(entry)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -107,6 +112,11 @@ def toggle_inter_vlan():
|
|||
return redirect(VIEW)
|
||||
|
||||
items[idx]['enabled'] = not items[idx].get('enabled', True)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -136,6 +146,11 @@ def edit_inter_vlan():
|
|||
|
||||
items[idx] = entry
|
||||
items[idx]['enabled'] = request.form.get('enabled') == 'on'
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -160,6 +175,11 @@ def delete_inter_vlan():
|
|||
return redirect(VIEW)
|
||||
|
||||
items.pop(idx)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_interface', __name__)
|
||||
|
||||
|
|
@ -53,6 +54,11 @@ def apply_interface():
|
|||
gen = core.setdefault('general', {})
|
||||
gen['wan_interface'] = wan
|
||||
gen['lan_interface'] = lan
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(_VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_mdns', __name__)
|
||||
|
||||
|
|
@ -23,6 +24,11 @@ def apply_mdns():
|
|||
'enabled': mdns_enabled,
|
||||
'reflect_vlans': mdns_reflect_vlans,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect('/view/view_mdns')
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_port_forwarding', __name__)
|
||||
|
||||
|
|
@ -84,6 +84,11 @@ def add_port_forward():
|
|||
|
||||
core = load_core()
|
||||
core.setdefault('port_forwarding', []).append(entry)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -108,6 +113,11 @@ def toggle_port_forward():
|
|||
return redirect(VIEW)
|
||||
|
||||
items[idx]['enabled'] = not items[idx].get('enabled', True)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -137,6 +147,11 @@ def edit_port_forward():
|
|||
|
||||
items[idx] = entry
|
||||
items[idx]['enabled'] = request.form.get('enabled') == 'on'
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -161,6 +176,11 @@ def delete_port_forward():
|
|||
return redirect(VIEW)
|
||||
|
||||
removed = items.pop(idx)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from flask import Blueprint, request, redirect, flash
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_upstream_dns', __name__)
|
||||
|
||||
|
|
@ -26,11 +27,8 @@ def apply_upstream_dns():
|
|||
return redirect('/view/view_upstream_dns')
|
||||
upstream_servers.append(clean)
|
||||
|
||||
try:
|
||||
cache_size = int(cache_size_raw)
|
||||
if cache_size < 0:
|
||||
raise ValueError
|
||||
except (ValueError, TypeError):
|
||||
cache_size = validate.int_range(cache_size_raw, 0, None)
|
||||
if cache_size is None:
|
||||
flash('Cache Size must be a non-negative integer.', 'error')
|
||||
return redirect('/view/view_upstream_dns')
|
||||
|
||||
|
|
@ -51,6 +49,11 @@ def apply_upstream_dns():
|
|||
'cache_size': cache_size,
|
||||
'upstream_servers': upstream_servers,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect('/view/view_upstream_dns')
|
||||
save_core(core)
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
return redirect('/view/view_upstream_dns')
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from auth import require_level
|
|||
from config_utils import load_core, save_core, verify_core_hash, queued_msg
|
||||
import sanitize
|
||||
import ipaddress as _ipaddress
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_vlans', __name__)
|
||||
|
||||
|
|
@ -77,6 +78,10 @@ def add_vlan():
|
|||
flash(f'VLAN {vlan_id} (derived from subnet) already exists.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
if radius_default and any(v.get('radius_default') for v in vlans):
|
||||
flash('Only one VLAN can be the RADIUS default.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
entry = {
|
||||
'vlan_id': vlan_id,
|
||||
'name': name,
|
||||
|
|
@ -92,6 +97,11 @@ def add_vlan():
|
|||
else:
|
||||
entry['reservations'] = []
|
||||
vlans.append(entry)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -161,6 +171,10 @@ def edit_vlan():
|
|||
flash(f'VLAN {vlan_id} (derived from subnet) already exists.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
if radius_default and any(i != idx and v.get('radius_default') for i, v in enumerate(vlans)):
|
||||
flash('Only one VLAN can be the RADIUS default.', 'error')
|
||||
return redirect(VIEW)
|
||||
|
||||
existing.update({
|
||||
'vlan_id': vlan_id,
|
||||
'name': name,
|
||||
|
|
@ -171,6 +185,11 @@ def edit_vlan():
|
|||
'mdns_reflection': mdns_reflection,
|
||||
'use_blocklists': use_blocklists,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
@ -195,6 +214,11 @@ def delete_vlan():
|
|||
return redirect(VIEW)
|
||||
|
||||
removed = vlans.pop(idx)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(VIEW)
|
||||
save_core(core)
|
||||
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from flask import Blueprint, make_response, redirect, flash, request
|
|||
from auth import require_level
|
||||
from config_utils import load_core, save_core, verify_core_hash, queued_msg, CONFIGS_DIR
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
|
||||
bp = Blueprint('action_apply_vpn', __name__)
|
||||
|
||||
|
|
@ -140,19 +140,16 @@ def _conf_response(vlan, peer_name, peer_ip, private_key):
|
|||
@require_level('administrator')
|
||||
def apply_vpn():
|
||||
listen_port_raw = request.form.get('vpn_listen_port', '').strip()
|
||||
server_endpoint = sanitize.domainname(request.form.get('vpn_server_endpoint', ''))
|
||||
domain = sanitize.domainname(request.form.get('vpn_domain', ''))
|
||||
server_endpoint = validate.domainname(request.form.get('vpn_server_endpoint', ''))
|
||||
domain = validate.domainname(request.form.get('vpn_domain', ''))
|
||||
dns_raw = request.form.get('vpn_dns_server', '').strip()
|
||||
mtu_raw = request.form.get('vpn_mtu', '').strip()
|
||||
|
||||
if not listen_port_raw:
|
||||
flash('Listen port is required.', 'error')
|
||||
return redirect(_VIEW)
|
||||
try:
|
||||
listen_port = int(listen_port_raw)
|
||||
if not (1 <= listen_port <= 65535):
|
||||
raise ValueError
|
||||
except (ValueError, TypeError):
|
||||
listen_port = validate.int_range(listen_port_raw, 1, 65535)
|
||||
if listen_port is None:
|
||||
flash(f'"{listen_port_raw}" is not a valid port number (1-65535).', 'error')
|
||||
return redirect(_VIEW)
|
||||
|
||||
|
|
@ -165,11 +162,8 @@ def apply_vpn():
|
|||
|
||||
mtu = None
|
||||
if mtu_raw:
|
||||
try:
|
||||
mtu = int(mtu_raw)
|
||||
if not (_MTU_MIN <= mtu <= _MTU_MAX):
|
||||
raise ValueError
|
||||
except (ValueError, TypeError):
|
||||
mtu = validate.int_range(mtu_raw, _MTU_MIN, _MTU_MAX)
|
||||
if mtu is None:
|
||||
flash(f'"{mtu_raw}" is not a valid MTU (must be {_MTU_MIN}-{_MTU_MAX}).', 'error')
|
||||
return redirect(_VIEW)
|
||||
|
||||
|
|
@ -182,6 +176,11 @@ def apply_vpn():
|
|||
flash('No WireGuard VLAN found in configuration.', 'error')
|
||||
return redirect(_VIEW)
|
||||
|
||||
for v in core.get('vlans', []):
|
||||
if v.get('is_vpn') and v is not vpn_vlan and v.get('vpn_information', {}).get('listen_port') == listen_port:
|
||||
flash(f'Listen port {listen_port} is already used by another VPN VLAN.', 'error')
|
||||
return redirect(_VIEW)
|
||||
|
||||
info = vpn_vlan.setdefault('vpn_information', {})
|
||||
info['listen_port'] = listen_port
|
||||
info['server_endpoint'] = server_endpoint
|
||||
|
|
@ -197,6 +196,11 @@ def apply_vpn():
|
|||
else:
|
||||
overrides.pop('mtu', None)
|
||||
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(_VIEW)
|
||||
save_core(core)
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
return redirect(_VIEW)
|
||||
|
|
@ -258,6 +262,11 @@ def add_vpn_peer():
|
|||
'split_tunnel': split_tunnel,
|
||||
'enabled': enabled,
|
||||
})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(_VIEW)
|
||||
save_core(core)
|
||||
|
||||
return _conf_response(vpn_vlan, peer_name, peer_ip, private_key)
|
||||
|
|
@ -293,6 +302,11 @@ def edit_vpn_peer():
|
|||
return redirect(_VIEW)
|
||||
|
||||
peers[peer_idx].update({'name': peer_name, 'split_tunnel': split_tunnel, 'enabled': enabled})
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(_VIEW)
|
||||
save_core(core)
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
return redirect(_VIEW)
|
||||
|
|
@ -316,6 +330,11 @@ def toggle_vpn_peer():
|
|||
|
||||
peers = vlan.get('peers', [])
|
||||
peers[peer_idx]['enabled'] = not peers[peer_idx].get('enabled', True)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(_VIEW)
|
||||
save_core(core)
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
return redirect(_VIEW)
|
||||
|
|
@ -338,6 +357,11 @@ def delete_vpn_peer():
|
|||
return redirect(_VIEW)
|
||||
|
||||
vlan.get('peers', []).pop(peer_idx)
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(_VIEW)
|
||||
save_core(core)
|
||||
flash(queued_msg('core apply'), 'success')
|
||||
return redirect(_VIEW)
|
||||
|
|
@ -362,6 +386,11 @@ def regenerate_vpn_peer():
|
|||
private_key, public_key = _generate_wg_keypair()
|
||||
peer = vlan['peers'][peer_idx]
|
||||
peer['public_key'] = public_key
|
||||
errors = validate.validate_config(core)
|
||||
if errors:
|
||||
for msg in errors:
|
||||
flash(msg, 'error')
|
||||
return redirect(_VIEW)
|
||||
save_core(core)
|
||||
|
||||
return _conf_response(vlan, peer['name'], peer['ip'], private_key)
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
"""
|
||||
validate.py -- Flask app re-export of the shared validation module.
|
||||
|
||||
~/router/validation.py is volume-mounted to /app/validation.py by
|
||||
docker-compose, making it directly importable. Action scripts import
|
||||
this module (validate.*) so they are insulated from the shared file's
|
||||
location and name.
|
||||
"""
|
||||
from validation import (
|
||||
VALID_PROTOCOLS,
|
||||
VALID_BLOCKLIST_FORMATS,
|
||||
ip,
|
||||
ip_or_cidr,
|
||||
port,
|
||||
banned_ip,
|
||||
)
|
||||
|
||||
VALID_DDNS_PROVIDERS = ('noip', 'cloudflare', 'duckdns')
|
||||
|
||||
__all__ = [
|
||||
'VALID_PROTOCOLS',
|
||||
'VALID_BLOCKLIST_FORMATS',
|
||||
'VALID_DDNS_PROVIDERS',
|
||||
'ip',
|
||||
'ip_or_cidr',
|
||||
'port',
|
||||
'banned_ip',
|
||||
]
|
||||
|
|
@ -2,7 +2,7 @@ from flask import Blueprint, session, redirect, get_flashed_messages
|
|||
from markupsafe import Markup
|
||||
import json, re, subprocess, os, sys, html as html_mod
|
||||
import sanitize
|
||||
import validate
|
||||
import validation as validate
|
||||
from datetime import datetime, timezone
|
||||
from config_utils import core_hash, get_pending_entries, _seconds_until_next_run, _format_timing, _is_locked, _lock_mtime
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue