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 d3f0900bbb AIP-56 - Create auth manager interface and very basic 
implementation of FAB auth manager (#32217)
d3f0900bbb is described below

commit d3f0900bbb68bda36f81475bf64d83d51ee32b22
Author: Vincent <[email protected]>
AuthorDate: Wed Jul 5 12:52:14 2023 -0400

    AIP-56 - Create auth manager interface and very basic implementation of FAB 
auth manager (#32217)
---
 airflow/auth/__init__.py                           | 17 ++++++++
 airflow/auth/managers/__init__.py                  | 17 ++++++++
 airflow/auth/managers/base_auth_manager.py         | 35 ++++++++++++++++
 airflow/auth/managers/fab/__init__.py              | 17 ++++++++
 airflow/auth/managers/fab/fab_auth_manager.py      | 41 ++++++++++++++++++
 airflow/config_templates/config.yml                |  7 ++++
 airflow/config_templates/default_airflow.cfg       |  3 ++
 airflow/configuration.py                           | 20 +++++++++
 airflow/www/extensions/init_jinja_globals.py       |  3 +-
 airflow/www/templates/appbuilder/navbar_right.html |  9 ++--
 tests/auh/__init__.py                              | 16 ++++++++
 tests/auh/managers/__init__.py                     | 16 ++++++++
 tests/auh/managers/fab/__init__.py                 | 16 ++++++++
 tests/auh/managers/fab/test_fab_auth_manager.py    | 48 ++++++++++++++++++++++
 tests/www/views/conftest.py                        |  1 +
 15 files changed, 259 insertions(+), 7 deletions(-)

diff --git a/airflow/auth/__init__.py b/airflow/auth/__init__.py
new file mode 100644
index 0000000000..217e5db960
--- /dev/null
+++ b/airflow/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/__init__.py 
b/airflow/auth/managers/__init__.py
new file mode 100644
index 0000000000..217e5db960
--- /dev/null
+++ b/airflow/auth/managers/__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/base_auth_manager.py 
b/airflow/auth/managers/base_auth_manager.py
new file mode 100644
index 0000000000..6a4bb86f16
--- /dev/null
+++ b/airflow/auth/managers/base_auth_manager.py
@@ -0,0 +1,35 @@
+#
+# 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
+
+from abc import abstractmethod
+
+from airflow.utils.log.logging_mixin import LoggingMixin
+
+
+class BaseAuthManager(LoggingMixin):
+    """
+    Class to derive in order to implement concrete auth managers.
+
+    Auth managers are responsible for any user management related operation 
such as login, logout, authz, ...
+    """
+
+    @abstractmethod
+    def get_user_name(self) -> str:
+        """Return the username associated to the user in session."""
+        ...
diff --git a/airflow/auth/managers/fab/__init__.py 
b/airflow/auth/managers/fab/__init__.py
new file mode 100644
index 0000000000..217e5db960
--- /dev/null
+++ b/airflow/auth/managers/fab/__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/fab_auth_manager.py 
b/airflow/auth/managers/fab/fab_auth_manager.py
new file mode 100644
index 0000000000..4203857168
--- /dev/null
+++ b/airflow/auth/managers/fab/fab_auth_manager.py
@@ -0,0 +1,41 @@
+#
+# 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
+
+from flask_login import current_user
+
+from airflow.auth.managers.base_auth_manager import BaseAuthManager
+
+
+class FabAuthManager(BaseAuthManager):
+    """
+    Flask-AppBuilder auth manager.
+
+    This auth manager is responsible for providing a backward compatible user 
management experience to users.
+    """
+
+    def get_user_name(self) -> str:
+        """
+        Return the username associated to the user in session.
+
+        For backward compatibility reasons, the username in FAB auth manager 
is the concatenation of the
+        first name and the last name.
+        """
+        first_name = current_user.first_name or ""
+        last_name = current_user.last_name or ""
+        return f"{first_name} {last_name}".strip()
diff --git a/airflow/config_templates/config.yml 
b/airflow/config_templates/config.yml
index d588a7f26e..273f2a36c7 100644
--- a/airflow/config_templates/config.yml
+++ b/airflow/config_templates/config.yml
@@ -70,6 +70,13 @@ core:
       type: string
       example: ~
       default: "SequentialExecutor"
+    auth_manager:
+      description: |
+        The auth manager class that airflow should use. Full import path to 
the auth manager class.
+      version_added: ~
+      type: string
+      example: ~
+      default: "airflow.auth.managers.fab.fab_auth_manager.FabAuthManager"
     parallelism:
       description: |
         This defines the maximum number of task instances that can run 
concurrently per scheduler in
diff --git a/airflow/config_templates/default_airflow.cfg 
b/airflow/config_templates/default_airflow.cfg
index ae6bdec085..439944022f 100644
--- a/airflow/config_templates/default_airflow.cfg
+++ b/airflow/config_templates/default_airflow.cfg
@@ -58,6 +58,9 @@ default_timezone = utc
 # full import path to the class when using a custom executor.
 executor = SequentialExecutor
 
+# The auth manager class that airflow should use. Full import path to the auth 
manager class.
+auth_manager = airflow.auth.managers.fab.fab_auth_manager.FabAuthManager
+
 # This defines the maximum number of task instances that can run concurrently 
per scheduler in
 # Airflow, regardless of the worker count. Generally this value, multiplied by 
the number of
 # schedulers in your cluster, is the maximum number of task instances with the 
running
diff --git a/airflow/configuration.py b/airflow/configuration.py
index 288595efbd..6a55a6c99c 100644
--- a/airflow/configuration.py
+++ b/airflow/configuration.py
@@ -42,6 +42,7 @@ from urllib.parse import urlsplit
 
 from typing_extensions import overload
 
+from airflow.auth.managers.base_auth_manager import BaseAuthManager
 from airflow.exceptions import AirflowConfigException
 from airflow.secrets import DEFAULT_SECRETS_SEARCH_PATH, BaseSecretsBackend
 from airflow.utils import yaml
@@ -1743,6 +1744,24 @@ def initialize_secrets_backends() -> 
list[BaseSecretsBackend]:
     return backend_list
 
 
+def initialize_auth_manager() -> BaseAuthManager:
+    """
+    Initialize auth manager.
+
+    * import user manager class
+    * instantiate it and return it
+    """
+    auth_manager_cls = conf.getimport(section="core", key="auth_manager")
+
+    if not auth_manager_cls:
+        raise AirflowConfigException(
+            "No auth manager defined in the config. "
+            "Please specify one using section/key [core/auth_manager]."
+        )
+
+    return auth_manager_cls()
+
+
 @functools.lru_cache(maxsize=None)
 def _DEFAULT_CONFIG() -> str:
     path = _default_config_file_path("default_airflow.cfg")
@@ -1808,4 +1827,5 @@ WEBSERVER_CONFIG = ""  # Set by initialize_config
 
 conf = initialize_config()
 secrets_backend_list = initialize_secrets_backends()
+auth_manager = initialize_auth_manager()
 conf.validate()
diff --git a/airflow/www/extensions/init_jinja_globals.py 
b/airflow/www/extensions/init_jinja_globals.py
index 0674a8e4a3..9ef948084c 100644
--- a/airflow/www/extensions/init_jinja_globals.py
+++ b/airflow/www/extensions/init_jinja_globals.py
@@ -21,7 +21,7 @@ import logging
 import pendulum
 
 import airflow
-from airflow.configuration import conf
+from airflow.configuration import auth_manager, conf
 from airflow.settings import IS_K8S_OR_K8SCELERY_EXECUTOR, STATE_COLORS
 from airflow.utils.net import get_hostname
 from airflow.utils.platform import get_airflow_git_version
@@ -68,6 +68,7 @@ def init_jinja_globals(app):
             "git_version": git_version,
             "k8s_or_k8scelery_executor": IS_K8S_OR_K8SCELERY_EXECUTOR,
             "rest_api_enabled": False,
+            "auth_manager": auth_manager,
             "config_test_connection": conf.get("core", "test_connection", 
fallback="Disabled"),
         }
 
diff --git a/airflow/www/templates/appbuilder/navbar_right.html 
b/airflow/www/templates/appbuilder/navbar_right.html
index 8e606c65b3..1ade0847a6 100644
--- a/airflow/www/templates/appbuilder/navbar_right.html
+++ b/airflow/www/templates/appbuilder/navbar_right.html
@@ -66,12 +66,9 @@
 {% if not current_user.is_anonymous %}
   <li class="dropdown">
     <a class="dropdown-toggle" href="#">
-       <span class="navbar-user-icon" title="{{g.user.get_full_name()}}">
-         {% if current_user.first_name and current_user.last_name %}
-           <span>{{ (current_user.first_name[0] + 
current_user.last_name[0]).upper() }}</span>
-         {% else %}
-          <span class="material-icons">person</span>
-         {% endif %}
+       <span class="navbar-user-icon" title="{{ auth_manager.get_user_name() 
}}">
+         {% set user_names = auth_manager.get_user_name().split(" ", 1) %}
+         <span>{% for name in user_names %}{{ name[0].upper() }}{% endfor 
%}</span>
        </span>
        <b class="caret"></b>
     </a>
diff --git a/tests/auh/__init__.py b/tests/auh/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tests/auh/__init__.py
@@ -0,0 +1,16 @@
+# 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/tests/auh/managers/__init__.py b/tests/auh/managers/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tests/auh/managers/__init__.py
@@ -0,0 +1,16 @@
+# 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/tests/auh/managers/fab/__init__.py 
b/tests/auh/managers/fab/__init__.py
new file mode 100644
index 0000000000..13a83393a9
--- /dev/null
+++ b/tests/auh/managers/fab/__init__.py
@@ -0,0 +1,16 @@
+# 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/tests/auh/managers/fab/test_fab_auth_manager.py 
b/tests/auh/managers/fab/test_fab_auth_manager.py
new file mode 100644
index 0000000000..16e5151495
--- /dev/null
+++ b/tests/auh/managers/fab/test_fab_auth_manager.py
@@ -0,0 +1,48 @@
+# 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
+
+from unittest import mock
+
+import pytest
+
+from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager
+from airflow.www.fab_security.sqla.models import User
+
+
[email protected]
+def auth_manager():
+    return FabAuthManager()
+
+
+class TestFabAuthManager:
+    @pytest.mark.parametrize(
+        "first_name,last_name,expected",
+        [
+            ("First", "Last", "First Last"),
+            ("First", None, "First"),
+            (None, "Last", "Last"),
+        ],
+    )
+    @mock.patch("flask_login.utils._get_user")
+    def test_get_user_name(self, mock_current_user, first_name, last_name, 
expected, auth_manager):
+        user = User()
+        user.first_name = first_name
+        user.last_name = last_name
+        mock_current_user.return_value = user
+
+        assert auth_manager.get_user_name() == expected
diff --git a/tests/www/views/conftest.py b/tests/www/views/conftest.py
index 209a106df6..25e3a6d20a 100644
--- a/tests/www/views/conftest.py
+++ b/tests/www/views/conftest.py
@@ -173,6 +173,7 @@ class _TemplateWithContext(NamedTuple):
             "scheduler_job",
             # airflow.www.views.AirflowBaseView.extra_args
             "macros",
+            "auth_manager",
         ]
         for key in keys_to_delete:
             del result[key]

Reply via email to