This is an automated email from the ASF dual-hosted git repository.

vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 48c17fb67b Move Rest API auth related methods to FAB auth manager 
(#34924)
48c17fb67b is described below

commit 48c17fb67b28f3899a5e8f5cf8178dc8c478148f
Author: Vincent <97131062+vincb...@users.noreply.github.com>
AuthorDate: Fri Oct 20 10:33:24 2023 -0400

    Move Rest API auth related methods to FAB auth manager (#34924)
---
 airflow/api/auth/backend/basic_auth.py             |  56 +--
 airflow/api/auth/backend/kerberos_auth.py          |  20 +-
 airflow/auth/managers/fab/api/__init__.py          |  17 +
 airflow/auth/managers/fab/api/auth/__init__.py     |  17 +
 .../auth/managers/fab/api/auth/backend/__init__.py |  17 +
 .../managers/fab}/api/auth/backend/basic_auth.py   |   5 +-
 .../managers/fab/api/auth/backend/kerberos_auth.py |  39 ++
 .../auth/managers/fab/security_manager/override.py | 460 ++++++++++++++++++++-
 airflow/www/fab_security/manager.py                | 438 --------------------
 tests/test_utils/www.py                            |   2 +-
 tests/www/views/test_session.py                    |   2 +-
 11 files changed, 588 insertions(+), 485 deletions(-)

diff --git a/airflow/api/auth/backend/basic_auth.py 
b/airflow/api/auth/backend/basic_auth.py
index 6d4b507a03..6f215b8c1c 100644
--- a/airflow/api/auth/backend/basic_auth.py
+++ b/airflow/api/auth/backend/basic_auth.py
@@ -14,56 +14,38 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-"""Basic authentication backend."""
-from __future__ import annotations
+"""
+This module is deprecated.
 
-from functools import wraps
-from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
+Please use :mod:`airflow.auth.managers.fab.api.auth.backend.basic_auth` 
instead.
+"""
+from __future__ import annotations
 
-from flask import Response, request
-from flask_appbuilder.const import AUTH_LDAP
-from flask_login import login_user
+import warnings
+from typing import TYPE_CHECKING, Any, Callable
 
-from airflow.utils.airflow_flask_app import get_airflow_app
+import airflow.auth.managers.fab.api.auth.backend.basic_auth as fab_basic_auth
+from airflow.exceptions import RemovedInAirflow3Warning
 
 if TYPE_CHECKING:
     from airflow.auth.managers.fab.models import User
 
 CLIENT_AUTH: tuple[str, str] | Any | None = None
 
-
-def init_app(_):
-    """Initialize authentication backend."""
+warnings.warn(
+    "This module is deprecated. Please use 
`airflow.auth.managers.fab.api.auth.backend.basic_auth` instead.",
+    RemovedInAirflow3Warning,
+    stacklevel=2,
+)
 
 
-T = TypeVar("T", bound=Callable)
+def init_app(_):
+    fab_basic_auth.init_app(_)
 
 
 def auth_current_user() -> User | None:
-    """Authenticate and set current user if Authorization header exists."""
-    auth = request.authorization
-    if auth is None or not auth.username or not auth.password:
-        return None
-
-    ab_security_manager = get_airflow_app().appbuilder.sm
-    user = None
-    if ab_security_manager.auth_type == AUTH_LDAP:
-        user = ab_security_manager.auth_user_ldap(auth.username, auth.password)
-    if user is None:
-        user = ab_security_manager.auth_user_db(auth.username, auth.password)
-    if user is not None:
-        login_user(user, remember=False)
-    return user
-
-
-def requires_authentication(function: T):
-    """Decorate functions that require authentication."""
+    return fab_basic_auth.auth_current_user()
 
-    @wraps(function)
-    def decorated(*args, **kwargs):
-        if auth_current_user() is not None:
-            return function(*args, **kwargs)
-        else:
-            return Response("Unauthorized", 401, {"WWW-Authenticate": "Basic"})
 
-    return cast(T, decorated)
+def requires_authentication(function: Callable):
+    return fab_basic_auth.requires_authentication(function)
diff --git a/airflow/api/auth/backend/kerberos_auth.py 
b/airflow/api/auth/backend/kerberos_auth.py
index e5458d0552..c632bd0dfe 100644
--- a/airflow/api/auth/backend/kerberos_auth.py
+++ b/airflow/api/auth/backend/kerberos_auth.py
@@ -16,6 +16,9 @@
 # under the License.
 from __future__ import annotations
 
+import warnings
+
+from airflow.exceptions import RemovedInAirflow3Warning
 from airflow.utils.airflow_flask_app import get_airflow_app
 
 #
@@ -46,7 +49,7 @@ from airflow.utils.airflow_flask_app import get_airflow_app
 import logging
 import os
 from functools import wraps
-from typing import Any, Callable, TypeVar, cast
+from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
 
 import kerberos
 from flask import Response, _request_ctx_stack as stack, g, make_response, 
request  # type: ignore
@@ -55,6 +58,9 @@ from requests_kerberos import HTTPKerberosAuth
 from airflow.configuration import conf
 from airflow.utils.net import getfqdn
 
+if TYPE_CHECKING:
+    from airflow.auth.managers.models.base_user import BaseUser
+
 log = logging.getLogger(__name__)
 
 
@@ -129,8 +135,16 @@ def _gssapi_authenticate(token):
 T = TypeVar("T", bound=Callable)
 
 
-def requires_authentication(function: T):
+def requires_authentication(function: T, find_user: Callable[[str], BaseUser] 
| None = None):
     """Decorate functions that require authentication with Kerberos."""
+    if not find_user:
+        warnings.warn(
+            "This module is deprecated. Please use "
+            "`airflow.auth.managers.fab.api.auth.backend.kerberos_auth` 
instead.",
+            RemovedInAirflow3Warning,
+            stacklevel=2,
+        )
+        find_user = get_airflow_app().appbuilder.sm.find_user
 
     @wraps(function)
     def decorated(*args, **kwargs):
@@ -140,7 +154,7 @@ def requires_authentication(function: T):
             token = "".join(header.split()[1:])
             return_code = _gssapi_authenticate(token)
             if return_code == kerberos.AUTH_GSS_COMPLETE:
-                g.user = 
get_airflow_app().appbuilder.sm.find_user(username=ctx.kerberos_user)
+                g.user = find_user(ctx.kerberos_user)
                 response = function(*args, **kwargs)
                 response = make_response(response)
                 if ctx.kerberos_token is not None:
diff --git a/airflow/auth/managers/fab/api/__init__.py 
b/airflow/auth/managers/fab/api/__init__.py
new file mode 100644
index 0000000000..217e5db960
--- /dev/null
+++ b/airflow/auth/managers/fab/api/__init__.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/airflow/auth/managers/fab/api/auth/__init__.py 
b/airflow/auth/managers/fab/api/auth/__init__.py
new file mode 100644
index 0000000000..217e5db960
--- /dev/null
+++ b/airflow/auth/managers/fab/api/auth/__init__.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/airflow/auth/managers/fab/api/auth/backend/__init__.py 
b/airflow/auth/managers/fab/api/auth/backend/__init__.py
new file mode 100644
index 0000000000..217e5db960
--- /dev/null
+++ b/airflow/auth/managers/fab/api/auth/backend/__init__.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/airflow/api/auth/backend/basic_auth.py 
b/airflow/auth/managers/fab/api/auth/backend/basic_auth.py
similarity index 99%
copy from airflow/api/auth/backend/basic_auth.py
copy to airflow/auth/managers/fab/api/auth/backend/basic_auth.py
index 6d4b507a03..aa7bdf303e 100644
--- a/airflow/api/auth/backend/basic_auth.py
+++ b/airflow/auth/managers/fab/api/auth/backend/basic_auth.py
@@ -31,14 +31,13 @@ if TYPE_CHECKING:
 
 CLIENT_AUTH: tuple[str, str] | Any | None = None
 
+T = TypeVar("T", bound=Callable)
+
 
 def init_app(_):
     """Initialize authentication backend."""
 
 
-T = TypeVar("T", bound=Callable)
-
-
 def auth_current_user() -> User | None:
     """Authenticate and set current user if Authorization header exists."""
     auth = request.authorization
diff --git a/airflow/auth/managers/fab/api/auth/backend/kerberos_auth.py 
b/airflow/auth/managers/fab/api/auth/backend/kerberos_auth.py
new file mode 100644
index 0000000000..b8c0ea0e5e
--- /dev/null
+++ b/airflow/auth/managers/fab/api/auth/backend/kerberos_auth.py
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import logging
+from functools import partial
+from typing import Any
+
+from requests_kerberos import HTTPKerberosAuth
+
+from airflow.api.auth.backend.kerberos_auth import (
+    init_app as base_init_app,
+    requires_authentication as base_requires_authentication,
+)
+from airflow.utils.airflow_flask_app import get_airflow_app
+
+log = logging.getLogger(__name__)
+
+CLIENT_AUTH: tuple[str, str] | Any | None = HTTPKerberosAuth(service="airflow")
+
+init_app = base_init_app
+requires_authentication = partial(
+    base_requires_authentication, 
find_user=get_airflow_app().appbuilder.sm.find_user
+)
diff --git a/airflow/auth/managers/fab/security_manager/override.py 
b/airflow/auth/managers/fab/security_manager/override.py
index cd5cb86804..be1fffa954 100644
--- a/airflow/auth/managers/fab/security_manager/override.py
+++ b/airflow/auth/managers/fab/security_manager/override.py
@@ -30,7 +30,18 @@ from typing import TYPE_CHECKING, Container, Iterable, 
Sequence
 import re2
 from flask import flash, g, session
 from flask_appbuilder import const
-from flask_appbuilder.const import AUTH_DB, AUTH_LDAP, AUTH_OAUTH, AUTH_OID, 
AUTH_REMOTE_USER
+from flask_appbuilder.const import (
+    AUTH_DB,
+    AUTH_LDAP,
+    AUTH_OAUTH,
+    AUTH_OID,
+    AUTH_REMOTE_USER,
+    LOGMSG_ERR_SEC_ADD_REGISTER_USER,
+    LOGMSG_ERR_SEC_AUTH_LDAP,
+    LOGMSG_ERR_SEC_AUTH_LDAP_TLS,
+    LOGMSG_WAR_SEC_LOGIN_FAILED,
+    LOGMSG_WAR_SEC_NOLDAP_OBJ,
+)
 from flask_appbuilder.models.sqla import Base
 from flask_appbuilder.models.sqla.interface import SQLAInterface
 from flask_babel import lazy_gettext
@@ -40,7 +51,7 @@ from itsdangerous import want_bytes
 from markupsafe import Markup
 from sqlalchemy import func, inspect, select
 from sqlalchemy.exc import MultipleResultsFound
-from werkzeug.security import generate_password_hash
+from werkzeug.security import check_password_hash, generate_password_hash
 
 from airflow.auth.managers.fab.fab_auth_manager import 
MAP_METHOD_NAME_TO_FAB_ACTION_NAME
 from airflow.auth.managers.fab.models import Action, Permission, RegisterUser, 
Resource, Role
@@ -329,6 +340,101 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
         """Oauth providers."""
         return self.appbuilder.app.config["OAUTH_PROVIDERS"]
 
+    @property
+    def auth_ldap_tls_cacertdir(self):
+        """LDAP TLS CA certificate directory."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTDIR"]
+
+    @property
+    def auth_ldap_tls_cacertfile(self):
+        """LDAP TLS CA certificate file."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTFILE"]
+
+    @property
+    def auth_ldap_tls_certfile(self):
+        """LDAP TLS certificate file."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CERTFILE"]
+
+    @property
+    def auth_ldap_tls_keyfile(self):
+        """LDAP TLS key file."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_KEYFILE"]
+
+    @property
+    def auth_ldap_allow_self_signed(self):
+        """LDAP allow self signed."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_ALLOW_SELF_SIGNED"]
+
+    @property
+    def auth_ldap_tls_demand(self):
+        """LDAP TLS demand."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_DEMAND"]
+
+    @property
+    def auth_ldap_server(self):
+        """Gets the LDAP server object."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_SERVER"]
+
+    @property
+    def auth_ldap_use_tls(self):
+        """Should LDAP use TLS."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_USE_TLS"]
+
+    @property
+    def auth_ldap_bind_user(self):
+        """LDAP bind user."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_BIND_USER"]
+
+    @property
+    def auth_ldap_bind_password(self):
+        """LDAP bind password."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_BIND_PASSWORD"]
+
+    @property
+    def auth_ldap_search(self):
+        """LDAP search object."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_SEARCH"]
+
+    @property
+    def auth_ldap_search_filter(self):
+        """LDAP search filter."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_SEARCH_FILTER"]
+
+    @property
+    def auth_ldap_uid_field(self):
+        """LDAP UID field."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_UID_FIELD"]
+
+    @property
+    def auth_ldap_firstname_field(self):
+        """LDAP first name field."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_FIRSTNAME_FIELD"]
+
+    @property
+    def auth_ldap_lastname_field(self):
+        """LDAP last name field."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_LASTNAME_FIELD"]
+
+    @property
+    def auth_ldap_email_field(self):
+        """LDAP email field."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_EMAIL_FIELD"]
+
+    @property
+    def auth_ldap_append_domain(self):
+        """LDAP append domain."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_APPEND_DOMAIN"]
+
+    @property
+    def auth_ldap_username_format(self):
+        """LDAP username format."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_USERNAME_FORMAT"]
+
+    @property
+    def auth_ldap_group_field(self) -> str:
+        """LDAP group field."""
+        return self.appbuilder.get_app.config["AUTH_LDAP_GROUP_FIELD"]
+
     @property
     def oauth_whitelists(self):
         warnings.warn(
@@ -1062,6 +1168,228 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
                 log.error(const.LOGMSG_ERR_SEC_DEL_PERMROLE, e)
                 self.get_session.rollback()
 
+    """
+    --------------------
+    Auth related methods
+    --------------------
+    """
+
+    def auth_user_ldap(self, username, password):
+        """
+        Authenticate user with LDAP.
+
+        NOTE: this depends on python-ldap module.
+
+        :param username: the username
+        :param password: the password
+        """
+        # If no username is provided, go away
+        if (username is None) or username == "":
+            return None
+
+        # Search the DB for this user
+        user = self.find_user(username=username)
+
+        # If user is not active, go away
+        if user and (not user.is_active):
+            return None
+
+        # If user is not registered, and not self-registration, go away
+        if (not user) and (not self.auth_user_registration):
+            return None
+
+        # Ensure python-ldap is installed
+        try:
+            import ldap
+        except ImportError:
+            log.error("python-ldap library is not installed")
+            return None
+
+        try:
+            # LDAP certificate settings
+            if self.auth_ldap_tls_cacertdir:
+                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, 
self.auth_ldap_tls_cacertdir)
+            if self.auth_ldap_tls_cacertfile:
+                ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, 
self.auth_ldap_tls_cacertfile)
+            if self.auth_ldap_tls_certfile:
+                ldap.set_option(ldap.OPT_X_TLS_CERTFILE, 
self.auth_ldap_tls_certfile)
+            if self.auth_ldap_tls_keyfile:
+                ldap.set_option(ldap.OPT_X_TLS_KEYFILE, 
self.auth_ldap_tls_keyfile)
+            if self.auth_ldap_allow_self_signed:
+                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, 
ldap.OPT_X_TLS_ALLOW)
+                ldap.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+            elif self.auth_ldap_tls_demand:
+                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, 
ldap.OPT_X_TLS_DEMAND)
+                ldap.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+
+            # Initialise LDAP connection
+            con = ldap.initialize(self.auth_ldap_server)
+            con.set_option(ldap.OPT_REFERRALS, 0)
+            if self.auth_ldap_use_tls:
+                try:
+                    con.start_tls_s()
+                except Exception:
+                    log.error(LOGMSG_ERR_SEC_AUTH_LDAP_TLS, 
self.auth_ldap_server)
+                    return None
+
+            # Define variables, so we can check if they are set in later steps
+            user_dn = None
+            user_attributes = {}
+
+            # Flow 1 - (Indirect Search Bind):
+            #  - in this flow, special bind credentials are used to perform the
+            #    LDAP search
+            #  - in this flow, AUTH_LDAP_SEARCH must be set
+            if self.auth_ldap_bind_user:
+                # Bind with AUTH_LDAP_BIND_USER/AUTH_LDAP_BIND_PASSWORD
+                # (authorizes for LDAP search)
+                self._ldap_bind_indirect(ldap, con)
+
+                # Search for `username`
+                #  - returns the `user_dn` needed for binding to validate 
credentials
+                #  - returns the `user_attributes` needed for
+                #    AUTH_USER_REGISTRATION/AUTH_ROLES_SYNC_AT_LOGIN
+                if self.auth_ldap_search:
+                    user_dn, user_attributes = self._search_ldap(ldap, con, 
username)
+                else:
+                    log.error("AUTH_LDAP_SEARCH must be set when using 
AUTH_LDAP_BIND_USER")
+                    return None
+
+                # If search failed, go away
+                if user_dn is None:
+                    log.info(LOGMSG_WAR_SEC_NOLDAP_OBJ, username)
+                    return None
+
+                # Bind with user_dn/password (validates credentials)
+                if not self._ldap_bind(ldap, con, user_dn, password):
+                    if user:
+                        self.update_user_auth_stat(user, False)
+
+                    # Invalid credentials, go away
+                    log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
+                    return None
+
+            # Flow 2 - (Direct Search Bind):
+            #  - in this flow, the credentials provided by the end-user are 
used
+            #    to perform the LDAP search
+            #  - in this flow, we only search LDAP if AUTH_LDAP_SEARCH is set
+            #     - features like AUTH_USER_REGISTRATION & 
AUTH_ROLES_SYNC_AT_LOGIN
+            #       will only work if AUTH_LDAP_SEARCH is set
+            else:
+                # Copy the provided username (so we can apply formatters)
+                bind_username = username
+
+                # update `bind_username` by applying AUTH_LDAP_APPEND_DOMAIN
+                #  - for Microsoft AD, which allows binding with 
userPrincipalName
+                if self.auth_ldap_append_domain:
+                    bind_username = bind_username + "@" + 
self.auth_ldap_append_domain
+
+                # Update `bind_username` by applying AUTH_LDAP_USERNAME_FORMAT
+                #  - for transforming the username into a DN,
+                #    for example: "uid=%s,ou=example,o=test"
+                if self.auth_ldap_username_format:
+                    bind_username = self.auth_ldap_username_format % 
bind_username
+
+                # Bind with bind_username/password
+                # (validates credentials & authorizes for LDAP search)
+                if not self._ldap_bind(ldap, con, bind_username, password):
+                    if user:
+                        self.update_user_auth_stat(user, False)
+
+                    # Invalid credentials, go away
+                    log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, bind_username)
+                    return None
+
+                # Search for `username` (if AUTH_LDAP_SEARCH is set)
+                #  - returns the `user_attributes`
+                #    needed for AUTH_USER_REGISTRATION/AUTH_ROLES_SYNC_AT_LOGIN
+                #  - we search on `username` not `bind_username`,
+                #    because AUTH_LDAP_APPEND_DOMAIN and 
AUTH_LDAP_USERNAME_FORMAT
+                #    would result in an invalid search filter
+                if self.auth_ldap_search:
+                    user_dn, user_attributes = self._search_ldap(ldap, con, 
username)
+
+                    # If search failed, go away
+                    if user_dn is None:
+                        log.info(LOGMSG_WAR_SEC_NOLDAP_OBJ, username)
+                        return None
+
+            # Sync the user's roles
+            if user and user_attributes and self.auth_roles_sync_at_login:
+                user.roles = self._ldap_calculate_user_roles(user_attributes)
+                log.debug("Calculated new roles for user=%r as: %s", user_dn, 
user.roles)
+
+            # If the user is new, register them
+            if (not user) and user_attributes and self.auth_user_registration:
+                user = self.add_user(
+                    username=username,
+                    first_name=self.ldap_extract(user_attributes, 
self.auth_ldap_firstname_field, ""),
+                    last_name=self.ldap_extract(user_attributes, 
self.auth_ldap_lastname_field, ""),
+                    email=self.ldap_extract(
+                        user_attributes,
+                        self.auth_ldap_email_field,
+                        f"{username}@email.notfound",
+                    ),
+                    role=self._ldap_calculate_user_roles(user_attributes),
+                )
+                log.debug("New user registered: %s", user)
+
+                # If user registration failed, go away
+                if not user:
+                    log.info(LOGMSG_ERR_SEC_ADD_REGISTER_USER, username)
+                    return None
+
+            # LOGIN SUCCESS (only if user is now registered)
+            if user:
+                self._rotate_session_id()
+                self.update_user_auth_stat(user)
+                return user
+            else:
+                return None
+
+        except ldap.LDAPError as e:
+            msg = None
+            if isinstance(e, dict):
+                msg = getattr(e, "message", None)
+            if (msg is not None) and ("desc" in msg):
+                log.error(LOGMSG_ERR_SEC_AUTH_LDAP, e.message["desc"])
+                return None
+            else:
+                log.error(e)
+                return None
+
+    def auth_user_db(self, username, password):
+        """
+        Authenticate user, auth db style.
+
+        :param username:
+            The username or registered email address
+        :param password:
+            The password, will be tested against hashed password on db
+        """
+        if username is None or username == "":
+            return None
+        user = self.find_user(username=username)
+        if user is None:
+            user = self.find_user(email=username)
+        if user is None or (not user.is_active):
+            # Balance failure and success
+            check_password_hash(
+                "pbkdf2:sha256:150000$Z3t6fmj2$22da622d94a1f8118"
+                "c0976a03d2f18f680bfff877c9a965db9eedc51bc0be87c",
+                "password",
+            )
+            log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
+            return None
+        elif check_password_hash(user.password, password):
+            self._rotate_session_id()
+            self.update_user_auth_stat(user, True)
+            return user
+        else:
+            self.update_user_auth_stat(user, False)
+            log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
+            return None
+
     def get_oauth_user_info(self, provider, resp):
         """
         Get the OAuth user information from different OAuth APIs.
@@ -1189,6 +1517,24 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
 
         return True
 
+    @staticmethod
+    def ldap_extract_list(ldap_dict: dict[str, list[bytes]], field_name: str) 
-> list[str]:
+        raw_list = ldap_dict.get(field_name, [])
+        # decode - removing empty strings
+        return [x.decode("utf-8") for x in raw_list if x.decode("utf-8")]
+
+    @staticmethod
+    def ldap_extract(ldap_dict: dict[str, list[bytes]], field_name: str, 
fallback: str) -> str:
+        raw_value = ldap_dict.get(field_name, [b""])
+        # decode - if empty string, default to fallback, otherwise take first 
element
+        return raw_value[0].decode("utf-8") or fallback
+
+    """
+    ---------------
+    Private methods
+    ---------------
+    """
+
     @staticmethod
     def _azure_parse_jwt(token):
         """
@@ -1235,3 +1581,113 @@ class 
FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
         jwt_decoded_payload = json.loads(decoded_payload.decode("utf-8"))
 
         return jwt_decoded_payload
+
+    def _ldap_bind_indirect(self, ldap, con) -> None:
+        """
+        Attempt to bind to LDAP using the AUTH_LDAP_BIND_USER.
+
+        :param ldap: The ldap module reference
+        :param con: The ldap connection
+        """
+        # always check AUTH_LDAP_BIND_USER is set before calling this method
+        assert self.auth_ldap_bind_user, "AUTH_LDAP_BIND_USER must be set"
+
+        try:
+            log.debug("LDAP bind indirect TRY with username: %r", 
self.auth_ldap_bind_user)
+            con.simple_bind_s(self.auth_ldap_bind_user, 
self.auth_ldap_bind_password)
+            log.debug("LDAP bind indirect SUCCESS with username: %r", 
self.auth_ldap_bind_user)
+        except ldap.INVALID_CREDENTIALS as ex:
+            log.error("AUTH_LDAP_BIND_USER and AUTH_LDAP_BIND_PASSWORD are not 
valid LDAP bind credentials")
+            raise ex
+
+    def _search_ldap(self, ldap, con, username):
+        """
+        Search LDAP for user.
+
+        :param ldap: The ldap module reference
+        :param con: The ldap connection
+        :param username: username to match with AUTH_LDAP_UID_FIELD
+        :return: ldap object array
+        """
+        # always check AUTH_LDAP_SEARCH is set before calling this method
+        assert self.auth_ldap_search, "AUTH_LDAP_SEARCH must be set"
+
+        # build the filter string for the LDAP search
+        if self.auth_ldap_search_filter:
+            filter_str = 
f"(&{self.auth_ldap_search_filter}({self.auth_ldap_uid_field}={username}))"
+        else:
+            filter_str = f"({self.auth_ldap_uid_field}={username})"
+
+        # build what fields to request in the LDAP search
+        request_fields = [
+            self.auth_ldap_firstname_field,
+            self.auth_ldap_lastname_field,
+            self.auth_ldap_email_field,
+        ]
+        if self.auth_roles_mapping:
+            request_fields.append(self.auth_ldap_group_field)
+
+        # perform the LDAP search
+        log.debug(
+            "LDAP search for %r with fields %s in scope %r", filter_str, 
request_fields, self.auth_ldap_search
+        )
+        raw_search_result = con.search_s(
+            self.auth_ldap_search, ldap.SCOPE_SUBTREE, filter_str, 
request_fields
+        )
+        log.debug("LDAP search returned: %s", raw_search_result)
+
+        # Remove any search referrals from results
+        search_result = [
+            (dn, attrs) for dn, attrs in raw_search_result if dn is not None 
and isinstance(attrs, dict)
+        ]
+
+        # only continue if 0 or 1 results were returned
+        if len(search_result) > 1:
+            log.error(
+                "LDAP search for %r in scope '%a' returned multiple results",
+                self.auth_ldap_search,
+                filter_str,
+            )
+            return None, None
+
+        try:
+            # extract the DN
+            user_dn = search_result[0][0]
+            # extract the other attributes
+            user_info = search_result[0][1]
+            # return
+            return user_dn, user_info
+        except (IndexError, NameError):
+            return None, None
+
+    @staticmethod
+    def _ldap_bind(ldap, con, dn: str, password: str) -> bool:
+        """Validates/binds the provided dn/password with the LDAP sever."""
+        try:
+            log.debug("LDAP bind TRY with username: %r", dn)
+            con.simple_bind_s(dn, password)
+            log.debug("LDAP bind SUCCESS with username: %r", dn)
+            return True
+        except ldap.INVALID_CREDENTIALS:
+            return False
+
+    def _ldap_calculate_user_roles(self, user_attributes: dict[str, 
list[bytes]]) -> list[str]:
+        user_role_objects = set()
+
+        # apply AUTH_ROLES_MAPPING
+        if self.auth_roles_mapping:
+            user_role_keys = self.ldap_extract_list(user_attributes, 
self.auth_ldap_group_field)
+            user_role_objects.update(self.get_roles_from_keys(user_role_keys))
+
+        # apply AUTH_USER_REGISTRATION
+        if self.auth_user_registration:
+            registration_role_name = self.auth_user_registration_role
+
+            # lookup registration role in flask db
+            fab_role = self.find_role(registration_role_name)
+            if fab_role:
+                user_role_objects.add(fab_role)
+            else:
+                log.warning("Can't find AUTH_USER_REGISTRATION role: %s", 
registration_role_name)
+
+        return list(user_role_objects)
diff --git a/airflow/www/fab_security/manager.py 
b/airflow/www/fab_security/manager.py
index 21ca6a511a..b9d63faf56 100644
--- a/airflow/www/fab_security/manager.py
+++ b/airflow/www/fab_security/manager.py
@@ -28,11 +28,7 @@ from flask import g, session, url_for
 from flask_appbuilder.const import (
     AUTH_DB,
     AUTH_LDAP,
-    LOGMSG_ERR_SEC_ADD_REGISTER_USER,
-    LOGMSG_ERR_SEC_AUTH_LDAP,
-    LOGMSG_ERR_SEC_AUTH_LDAP_TLS,
     LOGMSG_WAR_SEC_LOGIN_FAILED,
-    LOGMSG_WAR_SEC_NOLDAP_OBJ,
 )
 from flask_appbuilder.security.registerviews import (
     RegisterUserDBView,
@@ -60,7 +56,6 @@ from flask_appbuilder.security.views import (
 from flask_jwt_extended import current_user as current_user_jwt
 from flask_limiter import Limiter
 from flask_limiter.util import get_remote_address
-from werkzeug.security import check_password_hash
 
 from airflow.configuration import conf
 from airflow.www.extensions.init_auth_manager import get_auth_manager
@@ -238,16 +233,6 @@ class BaseSecurityManager:
         """Gets the admin role."""
         return self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
 
-    @property
-    def auth_ldap_server(self):
-        """Gets the LDAP server object."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_SERVER"]
-
-    @property
-    def auth_ldap_use_tls(self):
-        """Should LDAP use TLS."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_USE_TLS"]
-
     @property
     def auth_user_registration(self):
         """Will user self registration be allowed."""
@@ -273,96 +258,11 @@ class BaseSecurityManager:
         """Should roles be synced at login."""
         return self.appbuilder.get_app.config["AUTH_ROLES_SYNC_AT_LOGIN"]
 
-    @property
-    def auth_ldap_search(self):
-        """LDAP search object."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_SEARCH"]
-
-    @property
-    def auth_ldap_search_filter(self):
-        """LDAP search filter."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_SEARCH_FILTER"]
-
-    @property
-    def auth_ldap_bind_user(self):
-        """LDAP bind user."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_BIND_USER"]
-
-    @property
-    def auth_ldap_bind_password(self):
-        """LDAP bind password."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_BIND_PASSWORD"]
-
-    @property
-    def auth_ldap_append_domain(self):
-        """LDAP append domain."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_APPEND_DOMAIN"]
-
-    @property
-    def auth_ldap_username_format(self):
-        """LDAP username format."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_USERNAME_FORMAT"]
-
-    @property
-    def auth_ldap_uid_field(self):
-        """LDAP UID field."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_UID_FIELD"]
-
-    @property
-    def auth_ldap_group_field(self) -> str:
-        """LDAP group field."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_GROUP_FIELD"]
-
-    @property
-    def auth_ldap_firstname_field(self):
-        """LDAP first name field."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_FIRSTNAME_FIELD"]
-
-    @property
-    def auth_ldap_lastname_field(self):
-        """LDAP last name field."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_LASTNAME_FIELD"]
-
-    @property
-    def auth_ldap_email_field(self):
-        """LDAP email field."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_EMAIL_FIELD"]
-
     @property
     def auth_ldap_bind_first(self):
         """LDAP bind first."""
         return self.appbuilder.get_app.config["AUTH_LDAP_BIND_FIRST"]
 
-    @property
-    def auth_ldap_allow_self_signed(self):
-        """LDAP allow self signed."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_ALLOW_SELF_SIGNED"]
-
-    @property
-    def auth_ldap_tls_demand(self):
-        """LDAP TLS demand."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_DEMAND"]
-
-    @property
-    def auth_ldap_tls_cacertdir(self):
-        """LDAP TLS CA certificate directory."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTDIR"]
-
-    @property
-    def auth_ldap_tls_cacertfile(self):
-        """LDAP TLS CA certificate file."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTFILE"]
-
-    @property
-    def auth_ldap_tls_certfile(self):
-        """LDAP TLS certificate file."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CERTFILE"]
-
-    @property
-    def auth_ldap_tls_keyfile(self):
-        """LDAP TLS key file."""
-        return self.appbuilder.get_app.config["AUTH_LDAP_TLS_KEYFILE"]
-
     @property
     def openid_providers(self):
         """Openid providers."""
@@ -472,344 +372,6 @@ class BaseSecurityManager:
         if conf.get("webserver", "SESSION_BACKEND") == "database":
             session.sid = str(uuid4())
 
-    def auth_user_db(self, username, password):
-        """
-        Authenticate user, auth db style.
-
-        :param username:
-            The username or registered email address
-        :param password:
-            The password, will be tested against hashed password on db
-        """
-        if username is None or username == "":
-            return None
-        user = self.find_user(username=username)
-        if user is None:
-            user = self.find_user(email=username)
-        if user is None or (not user.is_active):
-            # Balance failure and success
-            check_password_hash(
-                "pbkdf2:sha256:150000$Z3t6fmj2$22da622d94a1f8118"
-                "c0976a03d2f18f680bfff877c9a965db9eedc51bc0be87c",
-                "password",
-            )
-            log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
-            return None
-        elif check_password_hash(user.password, password):
-            self._rotate_session_id()
-            self.update_user_auth_stat(user, True)
-            return user
-        else:
-            self.update_user_auth_stat(user, False)
-            log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
-            return None
-
-    def _search_ldap(self, ldap, con, username):
-        """
-        Search LDAP for user.
-
-        :param ldap: The ldap module reference
-        :param con: The ldap connection
-        :param username: username to match with AUTH_LDAP_UID_FIELD
-        :return: ldap object array
-        """
-        # always check AUTH_LDAP_SEARCH is set before calling this method
-        assert self.auth_ldap_search, "AUTH_LDAP_SEARCH must be set"
-
-        # build the filter string for the LDAP search
-        if self.auth_ldap_search_filter:
-            filter_str = 
f"(&{self.auth_ldap_search_filter}({self.auth_ldap_uid_field}={username}))"
-        else:
-            filter_str = f"({self.auth_ldap_uid_field}={username})"
-
-        # build what fields to request in the LDAP search
-        request_fields = [
-            self.auth_ldap_firstname_field,
-            self.auth_ldap_lastname_field,
-            self.auth_ldap_email_field,
-        ]
-        if self.auth_roles_mapping:
-            request_fields.append(self.auth_ldap_group_field)
-
-        # perform the LDAP search
-        log.debug(
-            "LDAP search for %r with fields %s in scope %r", filter_str, 
request_fields, self.auth_ldap_search
-        )
-        raw_search_result = con.search_s(
-            self.auth_ldap_search, ldap.SCOPE_SUBTREE, filter_str, 
request_fields
-        )
-        log.debug("LDAP search returned: %s", raw_search_result)
-
-        # Remove any search referrals from results
-        search_result = [
-            (dn, attrs) for dn, attrs in raw_search_result if dn is not None 
and isinstance(attrs, dict)
-        ]
-
-        # only continue if 0 or 1 results were returned
-        if len(search_result) > 1:
-            log.error(
-                "LDAP search for %r in scope '%a' returned multiple results",
-                self.auth_ldap_search,
-                filter_str,
-            )
-            return None, None
-
-        try:
-            # extract the DN
-            user_dn = search_result[0][0]
-            # extract the other attributes
-            user_info = search_result[0][1]
-            # return
-            return user_dn, user_info
-        except (IndexError, NameError):
-            return None, None
-
-    def _ldap_calculate_user_roles(self, user_attributes: dict[str, 
list[bytes]]) -> list[str]:
-        user_role_objects = set()
-
-        # apply AUTH_ROLES_MAPPING
-        if self.auth_roles_mapping:
-            user_role_keys = self.ldap_extract_list(user_attributes, 
self.auth_ldap_group_field)
-            user_role_objects.update(self.get_roles_from_keys(user_role_keys))
-
-        # apply AUTH_USER_REGISTRATION
-        if self.auth_user_registration:
-            registration_role_name = self.auth_user_registration_role
-
-            # lookup registration role in flask db
-            fab_role = self.find_role(registration_role_name)
-            if fab_role:
-                user_role_objects.add(fab_role)
-            else:
-                log.warning("Can't find AUTH_USER_REGISTRATION role: %s", 
registration_role_name)
-
-        return list(user_role_objects)
-
-    def _ldap_bind_indirect(self, ldap, con) -> None:
-        """
-        Attempt to bind to LDAP using the AUTH_LDAP_BIND_USER.
-
-        :param ldap: The ldap module reference
-        :param con: The ldap connection
-        """
-        # always check AUTH_LDAP_BIND_USER is set before calling this method
-        assert self.auth_ldap_bind_user, "AUTH_LDAP_BIND_USER must be set"
-
-        try:
-            log.debug("LDAP bind indirect TRY with username: %r", 
self.auth_ldap_bind_user)
-            con.simple_bind_s(self.auth_ldap_bind_user, 
self.auth_ldap_bind_password)
-            log.debug("LDAP bind indirect SUCCESS with username: %r", 
self.auth_ldap_bind_user)
-        except ldap.INVALID_CREDENTIALS as ex:
-            log.error("AUTH_LDAP_BIND_USER and AUTH_LDAP_BIND_PASSWORD are not 
valid LDAP bind credentials")
-            raise ex
-
-    @staticmethod
-    def _ldap_bind(ldap, con, dn: str, password: str) -> bool:
-        """Validates/binds the provided dn/password with the LDAP sever."""
-        try:
-            log.debug("LDAP bind TRY with username: %r", dn)
-            con.simple_bind_s(dn, password)
-            log.debug("LDAP bind SUCCESS with username: %r", dn)
-            return True
-        except ldap.INVALID_CREDENTIALS:
-            return False
-
-    @staticmethod
-    def ldap_extract(ldap_dict: dict[str, list[bytes]], field_name: str, 
fallback: str) -> str:
-        raw_value = ldap_dict.get(field_name, [b""])
-        # decode - if empty string, default to fallback, otherwise take first 
element
-        return raw_value[0].decode("utf-8") or fallback
-
-    @staticmethod
-    def ldap_extract_list(ldap_dict: dict[str, list[bytes]], field_name: str) 
-> list[str]:
-        raw_list = ldap_dict.get(field_name, [])
-        # decode - removing empty strings
-        return [x.decode("utf-8") for x in raw_list if x.decode("utf-8")]
-
-    def auth_user_ldap(self, username, password):
-        """
-        Authenticate user with LDAP.
-
-        NOTE: this depends on python-ldap module.
-
-        :param username: the username
-        :param password: the password
-        """
-        # If no username is provided, go away
-        if (username is None) or username == "":
-            return None
-
-        # Search the DB for this user
-        user = self.find_user(username=username)
-
-        # If user is not active, go away
-        if user and (not user.is_active):
-            return None
-
-        # If user is not registered, and not self-registration, go away
-        if (not user) and (not self.auth_user_registration):
-            return None
-
-        # Ensure python-ldap is installed
-        try:
-            import ldap
-        except ImportError:
-            log.error("python-ldap library is not installed")
-            return None
-
-        try:
-            # LDAP certificate settings
-            if self.auth_ldap_tls_cacertdir:
-                ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, 
self.auth_ldap_tls_cacertdir)
-            if self.auth_ldap_tls_cacertfile:
-                ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, 
self.auth_ldap_tls_cacertfile)
-            if self.auth_ldap_tls_certfile:
-                ldap.set_option(ldap.OPT_X_TLS_CERTFILE, 
self.auth_ldap_tls_certfile)
-            if self.auth_ldap_tls_keyfile:
-                ldap.set_option(ldap.OPT_X_TLS_KEYFILE, 
self.auth_ldap_tls_keyfile)
-            if self.auth_ldap_allow_self_signed:
-                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, 
ldap.OPT_X_TLS_ALLOW)
-                ldap.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
-            elif self.auth_ldap_tls_demand:
-                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, 
ldap.OPT_X_TLS_DEMAND)
-                ldap.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
-
-            # Initialise LDAP connection
-            con = ldap.initialize(self.auth_ldap_server)
-            con.set_option(ldap.OPT_REFERRALS, 0)
-            if self.auth_ldap_use_tls:
-                try:
-                    con.start_tls_s()
-                except Exception:
-                    log.error(LOGMSG_ERR_SEC_AUTH_LDAP_TLS, 
self.auth_ldap_server)
-                    return None
-
-            # Define variables, so we can check if they are set in later steps
-            user_dn = None
-            user_attributes = {}
-
-            # Flow 1 - (Indirect Search Bind):
-            #  - in this flow, special bind credentials are used to perform the
-            #    LDAP search
-            #  - in this flow, AUTH_LDAP_SEARCH must be set
-            if self.auth_ldap_bind_user:
-                # Bind with AUTH_LDAP_BIND_USER/AUTH_LDAP_BIND_PASSWORD
-                # (authorizes for LDAP search)
-                self._ldap_bind_indirect(ldap, con)
-
-                # Search for `username`
-                #  - returns the `user_dn` needed for binding to validate 
credentials
-                #  - returns the `user_attributes` needed for
-                #    AUTH_USER_REGISTRATION/AUTH_ROLES_SYNC_AT_LOGIN
-                if self.auth_ldap_search:
-                    user_dn, user_attributes = self._search_ldap(ldap, con, 
username)
-                else:
-                    log.error("AUTH_LDAP_SEARCH must be set when using 
AUTH_LDAP_BIND_USER")
-                    return None
-
-                # If search failed, go away
-                if user_dn is None:
-                    log.info(LOGMSG_WAR_SEC_NOLDAP_OBJ, username)
-                    return None
-
-                # Bind with user_dn/password (validates credentials)
-                if not self._ldap_bind(ldap, con, user_dn, password):
-                    if user:
-                        self.update_user_auth_stat(user, False)
-
-                    # Invalid credentials, go away
-                    log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
-                    return None
-
-            # Flow 2 - (Direct Search Bind):
-            #  - in this flow, the credentials provided by the end-user are 
used
-            #    to perform the LDAP search
-            #  - in this flow, we only search LDAP if AUTH_LDAP_SEARCH is set
-            #     - features like AUTH_USER_REGISTRATION & 
AUTH_ROLES_SYNC_AT_LOGIN
-            #       will only work if AUTH_LDAP_SEARCH is set
-            else:
-                # Copy the provided username (so we can apply formatters)
-                bind_username = username
-
-                # update `bind_username` by applying AUTH_LDAP_APPEND_DOMAIN
-                #  - for Microsoft AD, which allows binding with 
userPrincipalName
-                if self.auth_ldap_append_domain:
-                    bind_username = bind_username + "@" + 
self.auth_ldap_append_domain
-
-                # Update `bind_username` by applying AUTH_LDAP_USERNAME_FORMAT
-                #  - for transforming the username into a DN,
-                #    for example: "uid=%s,ou=example,o=test"
-                if self.auth_ldap_username_format:
-                    bind_username = self.auth_ldap_username_format % 
bind_username
-
-                # Bind with bind_username/password
-                # (validates credentials & authorizes for LDAP search)
-                if not self._ldap_bind(ldap, con, bind_username, password):
-                    if user:
-                        self.update_user_auth_stat(user, False)
-
-                    # Invalid credentials, go away
-                    log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, bind_username)
-                    return None
-
-                # Search for `username` (if AUTH_LDAP_SEARCH is set)
-                #  - returns the `user_attributes`
-                #    needed for AUTH_USER_REGISTRATION/AUTH_ROLES_SYNC_AT_LOGIN
-                #  - we search on `username` not `bind_username`,
-                #    because AUTH_LDAP_APPEND_DOMAIN and 
AUTH_LDAP_USERNAME_FORMAT
-                #    would result in an invalid search filter
-                if self.auth_ldap_search:
-                    user_dn, user_attributes = self._search_ldap(ldap, con, 
username)
-
-                    # If search failed, go away
-                    if user_dn is None:
-                        log.info(LOGMSG_WAR_SEC_NOLDAP_OBJ, username)
-                        return None
-
-            # Sync the user's roles
-            if user and user_attributes and self.auth_roles_sync_at_login:
-                user.roles = self._ldap_calculate_user_roles(user_attributes)
-                log.debug("Calculated new roles for user=%r as: %s", user_dn, 
user.roles)
-
-            # If the user is new, register them
-            if (not user) and user_attributes and self.auth_user_registration:
-                user = self.add_user(
-                    username=username,
-                    first_name=self.ldap_extract(user_attributes, 
self.auth_ldap_firstname_field, ""),
-                    last_name=self.ldap_extract(user_attributes, 
self.auth_ldap_lastname_field, ""),
-                    email=self.ldap_extract(
-                        user_attributes,
-                        self.auth_ldap_email_field,
-                        f"{username}@email.notfound",
-                    ),
-                    role=self._ldap_calculate_user_roles(user_attributes),
-                )
-                log.debug("New user registered: %s", user)
-
-                # If user registration failed, go away
-                if not user:
-                    log.info(LOGMSG_ERR_SEC_ADD_REGISTER_USER, username)
-                    return None
-
-            # LOGIN SUCCESS (only if user is now registered)
-            if user:
-                self._rotate_session_id()
-                self.update_user_auth_stat(user)
-                return user
-            else:
-                return None
-
-        except ldap.LDAPError as e:
-            msg = None
-            if isinstance(e, dict):
-                msg = getattr(e, "message", None)
-            if (msg is not None) and ("desc" in msg):
-                log.error(LOGMSG_ERR_SEC_AUTH_LDAP, e.message["desc"])
-                return None
-            else:
-                log.error(e)
-                return None
-
     def auth_user_oid(self, email):
         """
         Openid user Authentication.
diff --git a/tests/test_utils/www.py b/tests/test_utils/www.py
index ea0295af2c..bd9e604097 100644
--- a/tests/test_utils/www.py
+++ b/tests/test_utils/www.py
@@ -23,7 +23,7 @@ from airflow.models import Log
 
 
 def client_with_login(app, expected_response_code=302, **kwargs):
-    patch_path = "airflow.www.fab_security.manager.check_password_hash"
+    patch_path = 
"airflow.auth.managers.fab.security_manager.override.check_password_hash"
     with mock.patch(patch_path) as check_password_hash:
         check_password_hash.return_value = True
         client = app.test_client()
diff --git a/tests/www/views/test_session.py b/tests/www/views/test_session.py
index 822cf967fd..25f40a2870 100644
--- a/tests/www/views/test_session.py
+++ b/tests/www/views/test_session.py
@@ -79,7 +79,7 @@ def test_session_id_rotates(app, user_client):
     resp = user_client.get("/logout/")
     assert resp.status_code == 302
 
-    patch_path = "airflow.www.fab_security.manager.check_password_hash"
+    patch_path = 
"airflow.auth.managers.fab.security_manager.override.check_password_hash"
     with mock.patch(patch_path) as check_password_hash:
         check_password_hash.return_value = True
         resp = user_client.post("/login/", data={"username": "test_user", 
"password": "test_user"})

Reply via email to