Skip to content

Commit f5e6ff5

Browse files
committed
Remove duplicate code blocks left over from the upstream merge
The auto-merge kept both our and upstream's copies of two blocks that should have collapsed into one: the MFA brute-force lockout helpers in login_routes.py (plus a duplicate `import time`), and the alert-update readonly-fields guard in alerts_routes.py. Functionally harmless since Python just re-defines the same names, but confusing and worth cleaning up. Kept the better-commented (upstream) copy in each case; verified py_compile and a container boot after each removal.
1 parent 222c3c5 commit f5e6ff5

2 files changed

Lines changed: 0 additions & 80 deletions

File tree

source/app/blueprints/pages/login/login_routes.py

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import string
2525
import time
2626
import json
27-
import time
2827
from flask import Blueprint, flash
2928
from flask import redirect
3029
from flask import render_template
@@ -436,66 +435,6 @@ def _register_mfa_failure(user, reason):
436435
session.pop("username", None)
437436

438437

439-
# MFA hardening constants. Values are deliberately conservative — a legitimate
440-
# user mistypes their token occasionally; an attacker brute-forcing 10^6 TOTP
441-
# codes should not be able to linearly grind them.
442-
_MFA_MAX_ATTEMPTS = 5
443-
_MFA_LOCKOUT_SECONDS = 15 * 60
444-
445-
446-
def _get_pre_mfa_user():
447-
"""Return the user this session just passed password auth for, or None.
448-
449-
The pre_mfa_user_id marker is set exclusively by wrap_login_user after a
450-
successful password (or LDAP) check. Any MFA handler that runs without it
451-
is being hit directly by an attacker and must be refused.
452-
"""
453-
pre_mfa_user_id = session.get('pre_mfa_user_id')
454-
if pre_mfa_user_id is None:
455-
return None
456-
return get_user(pre_mfa_user_id, id_key='id')
457-
458-
459-
def _clear_pre_mfa_state(preserve_lockout=False):
460-
"""Clear the markers that prove this session just passed password auth.
461-
462-
preserve_lockout: when True, keep the lockout timestamp and fail counter
463-
so that an attacker cannot wipe a brute-force lockout simply by hitting
464-
/login again.
465-
"""
466-
session.pop('pre_mfa_user_id', None)
467-
session.pop('pending_mfa_secret', None)
468-
if not preserve_lockout:
469-
session.pop('mfa_fail_count', None)
470-
session.pop('mfa_lockout_until', None)
471-
472-
473-
def _mfa_is_locked_out():
474-
locked_until = session.get('mfa_lockout_until')
475-
if locked_until and locked_until > time.time():
476-
return True
477-
if locked_until and locked_until <= time.time():
478-
# Lockout expired — reset the counter so the user gets a fresh window.
479-
session.pop('mfa_lockout_until', None)
480-
session['mfa_fail_count'] = 0
481-
return False
482-
483-
484-
def _register_mfa_failure(user, reason):
485-
session['mfa_fail_count'] = session.get('mfa_fail_count', 0) + 1
486-
track_activity(
487-
f'Failed MFA {reason} for user {user.user} (attempt {session["mfa_fail_count"]}/{_MFA_MAX_ATTEMPTS})',
488-
ctx_less=True, display_in_ui=False
489-
)
490-
if session['mfa_fail_count'] >= _MFA_MAX_ATTEMPTS:
491-
session['mfa_lockout_until'] = time.time() + _MFA_LOCKOUT_SECONDS
492-
# Drop the pending-MFA marker so the attacker has to go back through
493-
# password auth before they get another burst of attempts. Preserve
494-
# the lockout timestamp so a fresh /login cannot wipe it.
495-
_clear_pre_mfa_state(preserve_lockout=True)
496-
session.pop('username', None)
497-
498-
499438
@app.route("/auth/mfa-setup", methods=["GET", "POST"])
500439
def mfa_setup():
501440
user = _get_pre_mfa_user()

source/app/blueprints/rest/alerts_routes.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,6 @@
6666

6767
alerts_rest_blueprint = Blueprint('alerts_rest', __name__)
6868

69-
# Fields that must be immutable on alert update. Allowing them via the API
70-
# lets a user with write access to one customer re-attribute an alert to a
71-
# customer they cannot see — planting fake alerts or (with an XSS vector)
72-
# making another user move an alert into an attacker-controlled customer.
73-
# See SBA-ADV-20260128-05 / CWE-863.
74-
_ALERT_UPDATE_READONLY_FIELDS = frozenset({
75-
'alert_id', # primary key, must not be rewritten
76-
'alert_customer_id', # ownership — re-attribution bypasses customer ACL
77-
'alert_creation_time', # audit integrity; set once at creation
78-
})
79-
80-
81-
def _strip_readonly_update_fields(payload):
82-
"""Remove fields that must never be mutated via the alert-update API."""
83-
if not isinstance(payload, dict):
84-
return payload
85-
return {k: v for k, v in payload.items() if k not in _ALERT_UPDATE_READONLY_FIELDS}
86-
87-
8869
# Fields that must be immutable on alert update. The web UI never changes
8970
# these, and allowing them via the API lets a user with write access to one
9071
# customer re-attribute an alert to a customer they cannot see — either to

0 commit comments

Comments
 (0)