Development
This commit is contained in:
parent
43c4cf380d
commit
f011594b04
10 changed files with 163 additions and 46 deletions
|
|
@ -62,13 +62,22 @@ def portal_save():
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
duration = 0
|
duration = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
exp_n = int(request.form.get('default_expiration_value', '0').strip() or '0')
|
||||||
|
exp_unit = request.form.get('default_expiration_unit', 'hours')
|
||||||
|
mult = {'hours': 3600, 'days': 86400}.get(exp_unit, 3600)
|
||||||
|
expiration = exp_n * mult if exp_n > 0 else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
expiration = 0
|
||||||
|
|
||||||
after = {
|
after = {
|
||||||
**existing,
|
**existing,
|
||||||
'portal_splash_title': splash_title,
|
'portal_splash_title': splash_title,
|
||||||
'portal_splash_text': splash_text,
|
'portal_splash_text': splash_text,
|
||||||
'portal_terms': terms,
|
'portal_terms': terms,
|
||||||
'require_username_password': require_upw,
|
'require_username_password': require_upw,
|
||||||
'default_session_seconds': duration,
|
'default_session_seconds': duration,
|
||||||
|
'default_expiration_seconds': expiration,
|
||||||
}
|
}
|
||||||
vlan['captive_portal'] = after
|
vlan['captive_portal'] = after
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@
|
||||||
"input_type": "number",
|
"input_type": "number",
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"value": "0",
|
"value": "0",
|
||||||
"hint": "How long portal access lasts after authentication. 0 = no expiration."
|
"hint": "How long portal access lasts after authentication. 0 = no session limit."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|
@ -191,6 +191,31 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "field_row",
|
||||||
|
"cols": 2,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Default Expiration Duration",
|
||||||
|
"name": "default_expiration_value",
|
||||||
|
"input_type": "number",
|
||||||
|
"min": 0,
|
||||||
|
"value": "0",
|
||||||
|
"hint": "How long after creation an account is valid before it permanently expires. 0 = never expires."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Unit",
|
||||||
|
"name": "default_expiration_unit",
|
||||||
|
"input_type": "select",
|
||||||
|
"options": [
|
||||||
|
{"value": "hours", "label": "Hours"},
|
||||||
|
{"value": "days", "label": "Days"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "button_row",
|
"type": "button_row",
|
||||||
"items": [
|
"items": [
|
||||||
|
|
|
||||||
|
|
@ -38,18 +38,20 @@ def collect_tokens(cfg):
|
||||||
text = cp.get('portal_splash_text', vlan.get('portal_splash_text', ''))
|
text = cp.get('portal_splash_text', vlan.get('portal_splash_text', ''))
|
||||||
terms = cp.get('portal_terms', vlan.get('portal_terms', []))
|
terms = cp.get('portal_terms', vlan.get('portal_terms', []))
|
||||||
require_upw = cp.get('require_username_password', vlan.get('require_username_password', False))
|
require_upw = cp.get('require_username_password', vlan.get('require_username_password', False))
|
||||||
duration = cp.get('default_session_seconds', vlan.get('default_session_seconds', 0))
|
duration = cp.get('default_session_seconds', vlan.get('default_session_seconds', 0))
|
||||||
|
expiration = cp.get('default_expiration_seconds', vlan.get('default_expiration_seconds', 0))
|
||||||
n = len(terms)
|
n = len(terms)
|
||||||
display_rows.append({
|
display_rows.append({
|
||||||
'vlan_name': vlan['name'],
|
'vlan_name': vlan['name'],
|
||||||
'portal_splash_title': title,
|
'portal_splash_title': title,
|
||||||
'portal_splash_text': text,
|
'portal_splash_text': text,
|
||||||
'portal_terms': terms,
|
'portal_terms': terms,
|
||||||
'portal_terms_display': f'{n} term{"s" if n != 1 else ""}' if n else '--',
|
'portal_terms_display': f'{n} term{"s" if n != 1 else ""}' if n else '--',
|
||||||
'require_upw': require_upw,
|
'require_upw': require_upw,
|
||||||
'require_username_password': require_upw,
|
'require_username_password': require_upw,
|
||||||
'default_session_seconds': duration,
|
'default_session_seconds': duration,
|
||||||
'session_display': _format_session(duration),
|
'default_expiration_seconds': expiration,
|
||||||
|
'session_display': _format_session(duration),
|
||||||
})
|
})
|
||||||
|
|
||||||
content = factory.load_json(f'{factory.PAGES_DIR}/captiveportal/content.json')
|
content = factory.load_json(f'{factory.PAGES_DIR}/captiveportal/content.json')
|
||||||
|
|
|
||||||
|
|
@ -187,8 +187,12 @@ def addedit():
|
||||||
|
|
||||||
enabled = 'enabled' in request.form
|
enabled = 'enabled' in request.form
|
||||||
session_seconds = _parse_session_seconds(
|
session_seconds = _parse_session_seconds(
|
||||||
request.form.get('session_seconds_value', ''),
|
request.form.get('session_duration_value', ''),
|
||||||
request.form.get('session_seconds_unit', 'hours'),
|
request.form.get('session_duration_unit', 'hours'),
|
||||||
|
)
|
||||||
|
expires_seconds = _parse_session_seconds(
|
||||||
|
request.form.get('expiration_duration_value', ''),
|
||||||
|
request.form.get('expiration_duration_unit', 'hours'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not username:
|
if not username:
|
||||||
|
|
@ -223,10 +227,10 @@ def addedit():
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""UPDATE credentials
|
"""UPDATE credentials
|
||||||
SET username=?, password=?, description=?, user_type=?, digest_type=?,
|
SET username=?, password=?, description=?, user_type=?, digest_type=?,
|
||||||
vlan=?, enabled=?, date_set=?, session_seconds=?
|
vlan=?, enabled=?, date_set=?, session_seconds=?, expires_seconds=?
|
||||||
WHERE id=?""",
|
WHERE id=?""",
|
||||||
(username, stored_password, description, user_type, stored_digest_type,
|
(username, stored_password, description, user_type, stored_digest_type,
|
||||||
vlan, int(enabled), date_set, session_seconds, existing['id']),
|
vlan, int(enabled), date_set, session_seconds, expires_seconds, existing['id']),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
|
|
@ -255,7 +259,7 @@ def addedit():
|
||||||
(username, password, description, user_type, digest_type, vlan, enabled, date_set, session_seconds, expires_seconds)
|
(username, password, description, user_type, digest_type, vlan, enabled, date_set, session_seconds, expires_seconds)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||||
(username, hashed, description, user_type, digest_type,
|
(username, hashed, description, user_type, digest_type,
|
||||||
vlan, int(enabled), int(time.time()), session_seconds, 0),
|
vlan, int(enabled), int(time.time()), session_seconds, expires_seconds),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
{
|
{
|
||||||
"type": "card",
|
"type": "card",
|
||||||
"id": "add-form",
|
"id": "add-form",
|
||||||
"label": "Add Credential",
|
"label": "Add User Account",
|
||||||
"client_requirement": "client_is_administrator+",
|
"client_requirement": "client_is_administrator+",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
@ -91,13 +91,14 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Credential Active",
|
"label": "Account Active",
|
||||||
"name": "enabled",
|
"name": "enabled",
|
||||||
"input_type": "checkbox"
|
"input_type": "checkbox",
|
||||||
|
"value": "true"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "raw_html",
|
"type": "raw_html",
|
||||||
"html": "<script id=\"captive-vlan-data\" type=\"application/json\">%CAPTIVE_VLAN_OPTIONS%</script><script id=\"page-flags\" type=\"application/json\">{\"pro\": %PRO_LICENSE_JS%, \"radius_session_seconds\": %RADIUS_DEFAULT_SESSION_SECONDS_JS%}</script>"
|
"html": "<script id=\"captive-vlan-data\" type=\"application/json\">%CAPTIVE_VLAN_OPTIONS%</script><script id=\"page-flags\" type=\"application/json\">{\"pro\": %PRO_LICENSE_JS%, \"radius_session_seconds\": %RADIUS_DEFAULT_SESSION_SECONDS_JS%, \"radius_expiration_seconds\": %RADIUS_DEFAULT_EXPIRATION_SECONDS_JS%}</script>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|
@ -170,17 +171,42 @@
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Valid For",
|
"label": "Session Duration",
|
||||||
"name": "session_seconds_value",
|
"name": "session_duration_value",
|
||||||
"input_type": "number",
|
"input_type": "number",
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"value": "0",
|
"value": "0",
|
||||||
"hint": "How long this credential is valid after creation. 0 = no expiration."
|
"hint": "0 = no session limit"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"label": "Unit",
|
"label": "Unit",
|
||||||
"name": "session_seconds_unit",
|
"name": "session_duration_unit",
|
||||||
|
"input_type": "select",
|
||||||
|
"options": [
|
||||||
|
{"value": "hours", "label": "Hours"},
|
||||||
|
{"value": "days", "label": "Days"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field_row",
|
||||||
|
"cols": 2,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Expiration Duration",
|
||||||
|
"name": "expiration_duration_value",
|
||||||
|
"input_type": "number",
|
||||||
|
"min": 0,
|
||||||
|
"value": "0",
|
||||||
|
"hint": "How long after creation an account is valid before it permanently expires. 0 = never expires."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Unit",
|
||||||
|
"name": "expiration_duration_unit",
|
||||||
"input_type": "select",
|
"input_type": "select",
|
||||||
"options": [
|
"options": [
|
||||||
{"value": "hours", "label": "Hours"},
|
{"value": "hours", "label": "Hours"},
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,9 @@ def collect_tokens(cfg):
|
||||||
tokens['RADIUS_DEFAULT_SESSION_SECONDS_JS'] = str(
|
tokens['RADIUS_DEFAULT_SESSION_SECONDS_JS'] = str(
|
||||||
cfg.get('radius', {}).get('options', {}).get('default_session_seconds', 0) or 0
|
cfg.get('radius', {}).get('options', {}).get('default_session_seconds', 0) or 0
|
||||||
)
|
)
|
||||||
|
tokens['RADIUS_DEFAULT_EXPIRATION_SECONDS_JS'] = str(
|
||||||
|
cfg.get('radius', {}).get('options', {}).get('default_expiration_seconds', 0) or 0
|
||||||
|
)
|
||||||
|
|
||||||
vlans = [v for v in cfg.get('vlans', []) if not v.get('is_vpn')]
|
vlans = [v for v in cfg.get('vlans', []) if not v.get('is_vpn')]
|
||||||
tokens['VLAN_OPTIONS'] = json.dumps(
|
tokens['VLAN_OPTIONS'] = json.dumps(
|
||||||
|
|
@ -84,10 +87,12 @@ def collect_tokens(cfg):
|
||||||
{
|
{
|
||||||
'value': v['name'],
|
'value': v['name'],
|
||||||
'label': f"{v['name']} (VLAN {v['vlan_id']})",
|
'label': f"{v['name']} (VLAN {v['vlan_id']})",
|
||||||
'require_upw': v.get('captive_portal', {}).get('require_username_password',
|
'require_upw': v.get('captive_portal', {}).get('require_username_password',
|
||||||
v.get('require_username_password', False)),
|
v.get('require_username_password', False)),
|
||||||
'default_session_seconds': v.get('captive_portal', {}).get('default_session_seconds',
|
'default_session_seconds': v.get('captive_portal', {}).get('default_session_seconds',
|
||||||
v.get('default_session_seconds', 0)),
|
v.get('default_session_seconds', 0)),
|
||||||
|
'default_expiration_seconds': v.get('captive_portal', {}).get('default_expiration_seconds',
|
||||||
|
v.get('default_expiration_seconds', 0)),
|
||||||
}
|
}
|
||||||
for v in captive_vlans
|
for v in captive_vlans
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -101,20 +101,29 @@ def auth_mode_save():
|
||||||
after['default_session_seconds'] = dur_n * mult if dur_n > 0 else 0
|
after['default_session_seconds'] = dur_n * mult if dur_n > 0 else 0
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
after['default_session_seconds'] = 0
|
after['default_session_seconds'] = 0
|
||||||
|
try:
|
||||||
|
exp_n = int(request.form.get('default_expiration_value', '0').strip() or '0')
|
||||||
|
exp_unit = request.form.get('default_expiration_unit', 'hours')
|
||||||
|
mult = {'hours': 3600, 'days': 86400}.get(exp_unit, 3600)
|
||||||
|
after['default_expiration_seconds'] = exp_n * mult if exp_n > 0 else 0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
after['default_expiration_seconds'] = 0
|
||||||
elif auth_mode == 'eap_certificate':
|
elif auth_mode == 'eap_certificate':
|
||||||
after['include_length'] = include_length
|
after['include_length'] = include_length
|
||||||
after['mab_first'] = mab_first
|
after['mab_first'] = mab_first
|
||||||
after.pop('eap_protocol', None)
|
after.pop('eap_protocol', None)
|
||||||
after.pop('tunneled_reply', None)
|
after.pop('tunneled_reply', None)
|
||||||
after.pop('inner_protocol', None)
|
after.pop('inner_protocol', None)
|
||||||
after.pop('default_session_seconds', None)
|
after.pop('default_session_seconds', None)
|
||||||
|
after.pop('default_expiration_seconds', None)
|
||||||
else: # mab
|
else: # mab
|
||||||
after.pop('eap_protocol', None)
|
after.pop('eap_protocol', None)
|
||||||
after.pop('tunneled_reply', None)
|
after.pop('tunneled_reply', None)
|
||||||
after.pop('inner_protocol', None)
|
after.pop('inner_protocol', None)
|
||||||
after.pop('include_length', None)
|
after.pop('include_length', None)
|
||||||
after.pop('mab_first', None)
|
after.pop('mab_first', None)
|
||||||
after.pop('default_session_seconds', None)
|
after.pop('default_session_seconds', None)
|
||||||
|
after.pop('default_expiration_seconds', None)
|
||||||
cfg.setdefault('radius', {})['options'] = after
|
cfg.setdefault('radius', {})['options'] = after
|
||||||
|
|
||||||
changes = config_utils.diff_fields(before, after)
|
changes = config_utils.diff_fields(before, after)
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@
|
||||||
"input_type": "number",
|
"input_type": "number",
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"value": "%RADIUS_DEFAULT_SESSION_VALUE%",
|
"value": "%RADIUS_DEFAULT_SESSION_VALUE%",
|
||||||
"hint": "How long a client session lasts before reauthentication is required. 0 = no expiration."
|
"hint": "How long a client session lasts before reauthentication is required. 0 = no session limit."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
|
|
@ -240,6 +240,32 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "field_row",
|
||||||
|
"cols": 2,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Default Expiration Duration",
|
||||||
|
"name": "default_expiration_value",
|
||||||
|
"input_type": "number",
|
||||||
|
"min": 0,
|
||||||
|
"value": "%RADIUS_DEFAULT_EXPIRATION_VALUE%",
|
||||||
|
"hint": "How long after creation an account is valid before it permanently expires. 0 = never expires."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Unit",
|
||||||
|
"name": "default_expiration_unit",
|
||||||
|
"input_type": "select",
|
||||||
|
"value": "%RADIUS_DEFAULT_EXPIRATION_UNIT%",
|
||||||
|
"options": [
|
||||||
|
{"value": "hours", "label": "Hours"},
|
||||||
|
{"value": "days", "label": "Days"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "raw_html",
|
"type": "raw_html",
|
||||||
"html": "</div>"
|
"html": "</div>"
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,17 @@ def collect_tokens(cfg):
|
||||||
tokens['RADIUS_DEFAULT_SESSION_VALUE'] = '0'
|
tokens['RADIUS_DEFAULT_SESSION_VALUE'] = '0'
|
||||||
tokens['RADIUS_DEFAULT_SESSION_UNIT'] = 'hours'
|
tokens['RADIUS_DEFAULT_SESSION_UNIT'] = 'hours'
|
||||||
|
|
||||||
|
exps = fr_opts.get('default_expiration_seconds', 0) or 0
|
||||||
|
if exps >= 86400 and exps % 86400 == 0:
|
||||||
|
tokens['RADIUS_DEFAULT_EXPIRATION_VALUE'] = str(exps // 86400)
|
||||||
|
tokens['RADIUS_DEFAULT_EXPIRATION_UNIT'] = 'days'
|
||||||
|
elif exps > 0:
|
||||||
|
tokens['RADIUS_DEFAULT_EXPIRATION_VALUE'] = str(max(1, round(exps / 3600)))
|
||||||
|
tokens['RADIUS_DEFAULT_EXPIRATION_UNIT'] = 'hours'
|
||||||
|
else:
|
||||||
|
tokens['RADIUS_DEFAULT_EXPIRATION_VALUE'] = '0'
|
||||||
|
tokens['RADIUS_DEFAULT_EXPIRATION_UNIT'] = 'hours'
|
||||||
|
|
||||||
vlans = cfg.get('vlans', [])
|
vlans = cfg.get('vlans', [])
|
||||||
default_vlan = next((v['name'] for v in vlans if v.get('radius_default') is True), '')
|
default_vlan = next((v['name'] for v in vlans if v.get('radius_default') is True), '')
|
||||||
vlan_options = [{'value': '', 'label': 'None (reject unknown devices)'}]
|
vlan_options = [{'value': '', 'label': 'None (reject unknown devices)'}]
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ def _load_supplicant_credentials():
|
||||||
conn = sqlite3.connect(str(CREDENTIALS_DB_FILE))
|
conn = sqlite3.connect(str(CREDENTIALS_DB_FILE))
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT username, password, digest_type, vlan, session_seconds, expires_seconds FROM credentials"
|
"SELECT username, password, digest_type, vlan, session_seconds, expires_seconds, date_set FROM credentials"
|
||||||
" WHERE user_type=? AND enabled=1",
|
" WHERE user_type=? AND enabled=1",
|
||||||
(USER_TYPE_SUPPLICANT,)
|
(USER_TYPE_SUPPLICANT,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
@ -145,7 +145,7 @@ def _supplicant_reply_attrs(cred, default_session_seconds, vlan_id, vlan):
|
||||||
attrs.append(f" Session-Timeout = {session}")
|
attrs.append(f" Session-Timeout = {session}")
|
||||||
expires = cred.get('expires_seconds') or 0
|
expires = cred.get('expires_seconds') or 0
|
||||||
if expires:
|
if expires:
|
||||||
dt = datetime.datetime.fromtimestamp(expires)
|
dt = datetime.datetime.fromtimestamp((cred.get('date_set') or 0) + expires)
|
||||||
attrs.append(f" Expiration := \"{dt.strftime('%b %d %Y %H:%M:%S')}\"")
|
attrs.append(f" Expiration := \"{dt.strftime('%b %d %Y %H:%M:%S')}\"")
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue