Skip to content

Login is impossible in pgAdmin 4 9.17 with Flask-Security-Too 5.8.2 because User.is_locked() has inverted semantics #10311

Description

@ApXNTekToP

Login is impossible in pgAdmin 4 9.17 with Flask-Security-Too 5.8.2 because User.is_locked() has inverted semantics

Environment

  • pgAdmin 4: 9.17
  • Python: 3.14
  • Installation: Python wheel / virtualenv
  • Mode: Server
  • Gunicorn: 26.0.0
  • Flask-Security-Too: 5.8.2
  • Flask-WTF: 1.3.0
  • WTForms: 3.2.2
  • Flask: 3.1.3

Description

An active internal Administrator user with a valid password cannot log in to pgAdmin.

Submitting correct credentials redirects the browser back to the login page:

POST /authenticate/login -> 302
Location: /login

Incorrect passwords are detected correctly.

However, when the correct password is entered, authentication still does not complete and the user is redirected back to /login.

User state

The affected user is valid and active:

{
  "id": 2,
  "username": "user@example.org",
  "email": "user@example.org",
  "active": true,
  "role": "Administrator",
  "auth_source": "internal",
  "locked": false
}

The same behavior was reproduced with multiple internal users.

Password verification

The password was verified directly through Flask-Security using the same pgAdmin environment:

user_found: True
active: True
locked: False
login_attempts: 0
password_valid: True

Therefore the problem is not caused by an incorrect password, inactive account, or locked account.

Login form diagnostics

The installed flask_security.forms.LoginForm.validate() contains:

if self.user.is_locked(self.ifield.errors):
    return False

This means the expected contract is:

is_locked() == True  -> user is locked -> validation fails
is_locked() == False -> user is not locked -> validation continues

Direct diagnostics of the login form produced:

=== BASE VALIDATION ===
is_submitted: True
meta.csrf: False
base_valid: True
errors: {}
form_errors: []

=== FULL LOGIN VALIDATION ===
valid: False
errors: {}
form_errors: []
user: <User 2>
user_authenticated: True
requires_confirmation: False

The base WTForms validation succeeds and the password is successfully authenticated, but the complete login validation returns False.

Root cause

The pgAdmin User.is_locked() implementation appears to use the opposite return semantics from those expected by Flask-Security-Too 5.8.2.

The installed implementation behaved effectively like:

def is_locked(self, form_error=None):
    if self.locked:
        if form_error is not None:
            form_error.append(
                gettext(
                    'Your account is locked. Please contact the Administrator.'
                )
            )

        return False

    return True

For an unlocked user:

self.locked == False

the method returns:

True

Flask-Security then executes:

if self.user.is_locked(self.ifield.errors):
    return False

which causes every unlocked user to fail login validation.

Workaround

Changing the return values to the following immediately restores login functionality:

def is_locked(self, form_error=None):
    if self.locked:
        if form_error is not None:
            form_error.append(
                gettext(
                    'Your account is locked. Please contact the Administrator.'
                )
            )

        return True

    return False

After restarting pgAdmin, login works normally.

Expected behavior

An active, unlocked internal user with a valid password should authenticate successfully.

User.is_locked() should return:

True  -> account is locked
False -> account is not locked

to match the contract expected by Flask-Security-Too's LoginForm.validate().

Actual behavior

For an unlocked user, User.is_locked() returns True.

As a result:

valid password
    ↓
user_authenticated = True
    ↓
user.is_locked(...) = True
    ↓
LoginForm.validate() = False
    ↓
302 Location: /login

Additional notes

This was reproduced with pgAdmin 4 9.17 installed from the Python package in a virtual environment and running in Server Mode through Gunicorn.

The workaround above is only a local vendor patch and will be overwritten when the pgAdmin package is upgraded or reinstalled.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions