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 dd0a8f935b3 Create initial documentation for `KeycloakAuthManager` 
(#54246)
dd0a8f935b3 is described below

commit dd0a8f935b3ed26baf749a28fb6420477146def7
Author: Vincent <[email protected]>
AuthorDate: Fri Aug 8 15:57:16 2025 -0400

    Create initial documentation for `KeycloakAuthManager` (#54246)
---
 .pre-commit-config.yaml                            |  10 +-
 contributing-docs/08_static_code_checks.rst        |   2 +
 dev/breeze/doc/images/output_static-checks.svg     |  12 +-
 dev/breeze/doc/images/output_static-checks.txt     |   2 +-
 dev/breeze/src/airflow_breeze/pre_commit_ids.py    |   1 +
 devel-common/src/docs/provider_conf.py             |  17 +-
 providers/keycloak/docs/api-ref/token-api-ref.rst  |  22 +++
 providers/keycloak/docs/auth-manager/index.rst     |  49 ++++++
 .../docs/auth-manager/manage/permissions.rst       |  22 +++
 .../keycloak/docs/auth-manager/manage/users.rst    |  23 +++
 .../keycloak/docs/auth-manager/setup/config.rst    |  60 +++++++
 providers/keycloak/docs/auth-manager/setup/env.rst |  33 ++++
 providers/keycloak/docs/auth-manager/token.rst     |  42 +++++
 providers/keycloak/docs/index.rst                  |   8 +
 .../keycloak/auth_manager/openapi/__init__.py      |  16 ++
 .../v2-keycloak-auth-manager-generated.yaml        | 175 +++++++++++++++++++++
 .../pre_commit/generate_openapi_spec_keycloak.py   |  38 +++++
 .../run_generate_openapi_spec_keycloak.py          |  45 ++++++
 18 files changed, 567 insertions(+), 10 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 80f46592710..09033969c0e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1532,9 +1532,15 @@ repos:
         language: python
         entry: ./scripts/ci/pre_commit/generate_openapi_spec_fab.py
         pass_filenames: false
-        files: 
^airflow-core/src/airflow/api_fastapi/.*\.py$|^airflow-core/src/airflow/api_fastapi/auth/managers/simple/.*\.py$|^providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/.*\.py$
-        exclude: ^airflow-core/src/airflow/api_fastapi/execution_api/.*
+        files: 
^providers/fab/src/airflow/providers/fab/auth_manager/api_fastapi/.*\.py$
         additional_dependencies: ['rich>=12.4.4', 
'openapi-spec-validator>=0.7.1']
+      - id: generate-openapi-spec-keycloak
+        name: Generate the FastAPI API spec for Keycloak
+        language: python
+        entry: ./scripts/ci/pre_commit/generate_openapi_spec_keycloak.py
+        pass_filenames: false
+        files: 
^providers/keycloak/src/airflow/providers/keycloak/auth_manager/.*\.py$
+        additional_dependencies: [ 'rich>=12.4.4', 
'openapi-spec-validator>=0.7.1' ]
       - id: check-i18n-json
         name: Check i18n files validity
         description: Check i18n files are valid json, have no TODOs, and 
auto-format them
diff --git a/contributing-docs/08_static_code_checks.rst 
b/contributing-docs/08_static_code_checks.rst
index 343dc651806..5b26739a8fc 100644
--- a/contributing-docs/08_static_code_checks.rst
+++ b/contributing-docs/08_static_code_checks.rst
@@ -295,6 +295,8 @@ require Breeze Docker image to be built locally.
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
 | generate-openapi-spec-fab                                 | Generate the 
FastAPI API spec for FAB                  | *       |
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
+| generate-openapi-spec-keycloak                            | Generate the 
FastAPI API spec for Keycloak             | *       |
++-----------------------------------------------------------+--------------------------------------------------------+---------+
 | generate-pypi-readme                                      | Generate PyPI 
README                                   |         |
 
+-----------------------------------------------------------+--------------------------------------------------------+---------+
 | generate-tasksdk-datamodels                               | Generate 
Datamodels for TaskSDK client                 | *       |
diff --git a/dev/breeze/doc/images/output_static-checks.svg 
b/dev/breeze/doc/images/output_static-checks.svg
index b78e3631bb5..be5d574d89b 100644
--- a/dev/breeze/doc/images/output_static-checks.svg
+++ b/dev/breeze/doc/images/output_static-checks.svg
@@ -379,12 +379,12 @@
 </text><text class="breeze-static-checks-r5" x="0" y="1069.2" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-43)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1069.2" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-43)">doctoc&#160;|&#160;end-of-file-fixer&#160;|&#160;fix-encoding-pragma&#160;|&#160;flynt&#160;|&#160;gci&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="b [...]
 </text><text class="breeze-static-checks-r5" x="0" y="1093.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-44)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1093.6" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-44)">generate-airflow-diagrams&#160;|&#160;generate-airflowctl-datamodels&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-stat [...]
 </text><text class="breeze-static-checks-r5" x="0" y="1118" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-45)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1118" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-45)">generate-airflowctl-help-images&#160;|&#160;generate-openapi-spec&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class=" [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1142.4" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-46)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1142.4" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-46)">generate-openapi-spec-fab&#160;|&#160;generate-pypi-readme&#160;|&#160;generate-tasksdk-datamodels&#160;|&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1142.4" textLength="12.2" 
clip-path="url(#breeze-static-checks [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1166.8" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-47)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1166.8" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-47)">generate-volumes-for-sources&#160;|&#160;go-mockery&#160;|&#160;go-mod-tidy&#160;|&#160;gofmt&#160;|&#160;identity&#160;|&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1166.8" textLength="12 [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1191.2" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-48)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1191.2" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-48)">insert-license&#160;|&#160;kubeconform&#160;|&#160;lint-chart-schema&#160;|&#160;lint-dockerfile&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="145 [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1215.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-49)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1215.6" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-49)">lint-helm-chart&#160;|&#160;lint-json-schema&#160;|&#160;lint-markdown&#160;|&#160;mixed-line-ending&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1215.6" text [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1240" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-50)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1240" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-50)">mypy-airflow-core&#160;|&#160;mypy-airflow-ctl&#160;|&#160;mypy-dev&#160;|&#160;mypy-devel-common&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1 [...]
-</text><text class="breeze-static-checks-r5" x="0" y="1264.4" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-51)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1264.4" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-51)">mypy-providers&#160;|&#160;mypy-task-sdk&#160;|&#160;pretty-format-json&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#16
 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1142.4" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-46)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1142.4" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-46)">generate-openapi-spec-fab&#160;|&#160;generate-openapi-spec-keycloak&#160;|&#160;generate-pypi-readme</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1142.4" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-46)" [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1166.8" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-47)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1166.8" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-47)">|&#160;generate-tasksdk-datamodels&#160;|&#160;generate-volumes-for-sources&#160;|&#160;go-mockery&#160;|&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1166.8" textLength="12.2" clip-p 
[...]
+</text><text class="breeze-static-checks-r5" x="0" y="1191.2" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-48)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1191.2" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-48)">go-mod-tidy&#160;|&#160;gofmt&#160;|&#160;identity&#160;|&#160;insert-license&#160;|&#160;kubeconform&#160;|&#160;lint-chart-schema</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1191.2" textLength="12.2" 
clip-path="url(# [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1215.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-49)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1215.6" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-49)">|&#160;lint-dockerfile&#160;|&#160;lint-helm-chart&#160;|&#160;lint-json-schema&#160;|&#160;lint-markdown&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1215.6" [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1240" textLength="12.2" 
clip-path="url(#breeze-static-checks-line-50)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1240" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-50)">mixed-line-ending&#160;|&#160;mypy-airflow-core&#160;|&#160;mypy-airflow-ctl&#160;|&#160;mypy-dev&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1 [...]
+</text><text class="breeze-static-checks-r5" x="0" y="1264.4" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-51)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1264.4" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-51)">mypy-devel-common&#160;|&#160;mypy-providers&#160;|&#160;mypy-task-sdk&#160;|&#160;pretty-format-json&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1264.4" textLengt [...]
 </text><text class="breeze-static-checks-r5" x="0" y="1288.8" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-52)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1288.8" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-52)">prevent-deprecated-sqlalchemy-usage&#160;|&#160;provider-version-compat&#160;|&#160;pylint&#160;|&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1288.8" textLength="12 [...]
 </text><text class="breeze-static-checks-r5" x="0" y="1313.2" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-53)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1313.2" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-53)">python-no-log-warn&#160;|&#160;replace-bad-characters&#160;|&#160;rst-backticks&#160;|&#160;ruff&#160;|&#160;ruff-format&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1313.2" textLength="12.2" 
clip-path="url(#breez [...]
 </text><text class="breeze-static-checks-r5" x="0" y="1337.6" 
textLength="12.2" clip-path="url(#breeze-static-checks-line-54)">│</text><text 
class="breeze-static-checks-r7" x="451.4" y="1337.6" textLength="988.2" 
clip-path="url(#breeze-static-checks-line-54)">|&#160;shellcheck&#160;|&#160;trailing-whitespace&#160;|&#160;ts-compile-lint-simple-auth-manager-ui&#160;|&#160;&#160;&#160;&#160;</text><text
 class="breeze-static-checks-r5" x="1451.8" y="1337.6" textLength="12.2" 
clip-path="url(# [...]
diff --git a/dev/breeze/doc/images/output_static-checks.txt 
b/dev/breeze/doc/images/output_static-checks.txt
index 5d370fd0d00..9db41863d21 100644
--- a/dev/breeze/doc/images/output_static-checks.txt
+++ b/dev/breeze/doc/images/output_static-checks.txt
@@ -1 +1 @@
-1ff02ab3c1c4049df0ca8b48c6e0436e
+5eaaf5cd76e63aae4a774847c869e11f
diff --git a/dev/breeze/src/airflow_breeze/pre_commit_ids.py 
b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
index bfa9aa2c19c..2dbdf86ea86 100644
--- a/dev/breeze/src/airflow_breeze/pre_commit_ids.py
+++ b/dev/breeze/src/airflow_breeze/pre_commit_ids.py
@@ -111,6 +111,7 @@ PRE_COMMIT_LIST = [
     "generate-airflowctl-help-images",
     "generate-openapi-spec",
     "generate-openapi-spec-fab",
+    "generate-openapi-spec-keycloak",
     "generate-pypi-readme",
     "generate-tasksdk-datamodels",
     "generate-volumes-for-sources",
diff --git a/devel-common/src/docs/provider_conf.py 
b/devel-common/src/docs/provider_conf.py
index e2794f00bb2..3c65f2d0d2e 100644
--- a/devel-common/src/docs/provider_conf.py
+++ b/devel-common/src/docs/provider_conf.py
@@ -119,7 +119,7 @@ smartquotes_excludes = SMARTQUOTES_EXCLUDES
 # ones.
 extensions = BASIC_SPHINX_EXTENSIONS
 
-PROVIDER_PACKAGES_WITH_REDOC = ["apache-airflow-providers-fab"]
+PROVIDER_PACKAGES_WITH_REDOC = ["apache-airflow-providers-fab", 
"apache-airflow-providers-keycloak"]
 
 if PACKAGE_NAME in PROVIDER_PACKAGES_WITH_REDOC:
     extensions.extend(
@@ -356,6 +356,9 @@ if PACKAGE_NAME in PROVIDER_PACKAGES_WITH_REDOC:
         __file__ as fab_auth_manager_fastapi_api_file,
     )
     from airflow.providers.fab.auth_manager.openapi import __file__ as 
fab_auth_manager_flask_api_file
+    from airflow.providers.keycloak.auth_manager.openapi import (
+        __file__ as keycloak_auth_manager_fastapi_api_file,
+    )
 
     fab_auth_manager_flask_api_path = 
Path(fab_auth_manager_flask_api_file).parent.joinpath(
         "v1-flask-api.yaml"
@@ -363,6 +366,9 @@ if PACKAGE_NAME in PROVIDER_PACKAGES_WITH_REDOC:
     fab_auth_manager_fastapi_api_path = 
Path(fab_auth_manager_fastapi_api_file).parent.joinpath(
         "v2-fab-auth-manager-generated.yaml"
     )
+    keycloak_auth_manager_fastapi_api_path = 
Path(keycloak_auth_manager_fastapi_api_file).parent.joinpath(
+        "v2-keycloak-auth-manager-generated.yaml"
+    )
     redoc = [
         {
             "name": "Fab auth manager API",
@@ -381,4 +387,13 @@ if PACKAGE_NAME in PROVIDER_PACKAGES_WITH_REDOC:
                 "no-auto-auth": True,
             },
         },
+        {
+            "name": "Keycloak auth manager token API",
+            "page": "api-ref/token-api-ref",
+            "spec": keycloak_auth_manager_fastapi_api_path.as_posix(),
+            "opts": {
+                "hide-hostname": True,
+                "no-auto-auth": True,
+            },
+        },
     ]
diff --git a/providers/keycloak/docs/api-ref/token-api-ref.rst 
b/providers/keycloak/docs/api-ref/token-api-ref.rst
new file mode 100644
index 00000000000..e9003e082d0
--- /dev/null
+++ b/providers/keycloak/docs/api-ref/token-api-ref.rst
@@ -0,0 +1,22 @@
+
+ .. 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.
+
+Keycloak auth manager token API
+===============================
+
+It's a stub file. It will be automatically converted during the build process 
into valid documentation by the Sphinx plugin. See: /docs/conf.py
diff --git a/providers/keycloak/docs/auth-manager/index.rst 
b/providers/keycloak/docs/auth-manager/index.rst
new file mode 100644
index 00000000000..9dc2a231d97
--- /dev/null
+++ b/providers/keycloak/docs/auth-manager/index.rst
@@ -0,0 +1,49 @@
+ .. 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.
+
+=====================
+Keycloak auth manager
+=====================
+
+.. warning::
+  The Keycloak auth manager is alpha/experimental at the moment and may be 
subject to change without warning.
+
+The Keycloak auth manager is an authentication and authorization manager that 
uses Keycloak as the identity provider.
+It enables you to manage users, roles, groups, and permissions entirely within 
Keycloak.
+
+**Getting started**
+
+.. toctree::
+    :maxdepth: 2
+
+    setup/env
+    setup/config
+
+**Manage the environment**
+
+.. toctree::
+    :maxdepth: 2
+
+    manage/users
+    manage/permissions
+
+**Generate a JWT token to use Airflow public API**
+
+.. toctree::
+    :maxdepth: 2
+
+    token
diff --git a/providers/keycloak/docs/auth-manager/manage/permissions.rst 
b/providers/keycloak/docs/auth-manager/manage/permissions.rst
new file mode 100644
index 00000000000..644c21e963c
--- /dev/null
+++ b/providers/keycloak/docs/auth-manager/manage/permissions.rst
@@ -0,0 +1,22 @@
+ .. 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.
+
+==================================================
+Manage user permissions with Keycloak auth manager
+==================================================
+
+To be completed
diff --git a/providers/keycloak/docs/auth-manager/manage/users.rst 
b/providers/keycloak/docs/auth-manager/manage/users.rst
new file mode 100644
index 00000000000..b1cb4028536
--- /dev/null
+++ b/providers/keycloak/docs/auth-manager/manage/users.rst
@@ -0,0 +1,23 @@
+ .. 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.
+
+=======================================
+Manage users with Keycloak auth manager
+=======================================
+
+All users having access to the Airflow environment must be defined in Keycloak.
+To understand better how to manage users and groups in Keycloak, best is to 
refer to `official Keycloak documentation 
<https://www.keycloak.org/docs/latest/server_admin/index.html#assembly-managing-users_server_administration_guide>`__.
diff --git a/providers/keycloak/docs/auth-manager/setup/config.rst 
b/providers/keycloak/docs/auth-manager/setup/config.rst
new file mode 100644
index 00000000000..8a89194d062
--- /dev/null
+++ b/providers/keycloak/docs/auth-manager/setup/config.rst
@@ -0,0 +1,60 @@
+ .. 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.
+
+==================================================
+Configure Airflow to use the Keycloak auth manager
+==================================================
+
+To use the Keycloak auth manager in your Airflow environment, you must 
configure it in your Airflow configuration.
+
+.. note::
+  These configuration options must be set on the hosts/environments running 
the Airflow API server.
+
+.. code-block:: ini
+
+  [core]
+  auth_manager = 
airflow.providers.keycloak.auth_manager.keycloak_auth_manager.KeycloakAuthManager
+
+or
+
+.. code-block:: bash
+
+  export 
AIRFLOW__CORE__AUTH_MANAGER='airflow.providers.keycloak.auth_manager.keycloak_auth_manager.KeycloakAuthManager'
+
+Additionally, you'll need to provide some Keycloak-specific configuration 
options.
+
+Config Options
+--------------
+
+There are a number of configuration options available, which can either be set 
directly in the airflow.cfg file under
+an ``keycloak_auth_manager`` section or via environment variables using the 
``AIRFLOW__KEYCLOAK_AUTH_MANAGER__<OPTION_NAME>`` format,
+for example ``AIRFLOW__KEYCLOAK_AUTH_MANAGER__REALM = "Airflow"``. For
+more information on how to set these options, see 
`<https://airflow.apache.org/docs/apache-airflow/stable/howto/set-config.html>`__.
+
+Required config options:
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+- ``client_id``. Client ID configured in Keycloak to integrate with Airflow. 
This client must follow the standard ``OpenID Connect authentication flow``.
+- ``client_secret``. Secret associated to the client configured in Keycloak to 
integrate with Airflow.
+- ``realm``. Realm configured in Keycloak associated to Airflow. This realm 
define all users, roles, groups and permissions used by Airflow.
+
+Optional config options:
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+- ``server_url``. Keycloak server URL. This server URL is used by the Airflow 
API server to communicate with Keycloak.
+  If the Airflow API server and Keycloak are running in Docker, set 
"http://host.docker.internal:<PORT>" (default value).
+  You do not need to set this configuration option if you are running Keycloak 
with Breeze.
diff --git a/providers/keycloak/docs/auth-manager/setup/env.rst 
b/providers/keycloak/docs/auth-manager/setup/env.rst
new file mode 100644
index 00000000000..cc7a1b012dd
--- /dev/null
+++ b/providers/keycloak/docs/auth-manager/setup/env.rst
@@ -0,0 +1,33 @@
+ .. 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.
+
+============================
+Setup a Keycloak environment
+============================
+
+To use the Keycloak auth manager, you must have a running Keycloak environment.
+This is where you define users, groups, roles, and permissions.
+Airflow delegates all authentication and authorization operations to Keycloak, 
querying it for every access decision.
+
+If you don't have a Keycloak environment set up, please refer to the `official 
Keycloak documentation <https://www.keycloak.org/guides>`__ for installation 
and configuration guidance.
+
+Setup and run Keycloak with Breeze (local development)
+------------------------------------------------------
+
+Setting up and running a Keycloak environment with Breeze for local 
development is straightforward.
+Simply run ``breeze start-airflow --integration keycloak``. Breeze will handle 
starting Keycloak in a separate container.
+The Keycloak admin console will be available at http://localhost:48080/.
diff --git a/providers/keycloak/docs/auth-manager/token.rst 
b/providers/keycloak/docs/auth-manager/token.rst
new file mode 100644
index 00000000000..b38904b3516
--- /dev/null
+++ b/providers/keycloak/docs/auth-manager/token.rst
@@ -0,0 +1,42 @@
+ .. 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.
+
+Generate JWT token with Keycloak auth manager
+=============================================
+
+.. note::
+    This guide only applies if your environment is configured with Keycloak 
auth manager.
+
+In order to use the :doc:`Airflow public API 
<apache-airflow:stable-rest-api-ref>`, you need a JWT token for authentication.
+You can then include this token in your Airflow public API requests.
+To generate a JWT token, use the ``Create Token`` API in 
:doc:`/api-ref/token-api-ref`.
+
+Example
+'''''''
+
+.. code-block:: bash
+
+    ENDPOINT_URL="http://localhost:8080";
+    curl -X 'POST' \
+        "${ENDPOINT_URL}/auth/token" \
+        -H 'Content-Type: application/json' \
+        -d '{
+        "username": "<username>",
+        "password": "<password>"
+        }'
+
+This process will return a token that you can use in the Airflow public API 
requests.
diff --git a/providers/keycloak/docs/index.rst 
b/providers/keycloak/docs/index.rst
index 7e235841eda..2a98598bf8a 100644
--- a/providers/keycloak/docs/index.rst
+++ b/providers/keycloak/docs/index.rst
@@ -29,6 +29,13 @@
     Changelog <changelog>
     Security <security>
 
+.. toctree::
+    :hidden:
+    :maxdepth: 1
+    :caption: Guides
+
+    Keycloak auth manager <auth-manager/index>
+
 .. toctree::
     :hidden:
     :maxdepth: 1
@@ -36,6 +43,7 @@
 
     Python API <_api/airflow/providers/keycloak/index>
     Configuration <configurations-ref>
+    Keycloak auth manager token API <api-ref/token-api-ref>
 
 .. toctree::
     :hidden:
diff --git 
a/providers/keycloak/src/airflow/providers/keycloak/auth_manager/openapi/__init__.py
 
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/openapi/__init__.py
new file mode 100644
index 00000000000..13a83393a91
--- /dev/null
+++ 
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/openapi/__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/providers/keycloak/src/airflow/providers/keycloak/auth_manager/openapi/v2-keycloak-auth-manager-generated.yaml
 
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/openapi/v2-keycloak-auth-manager-generated.yaml
new file mode 100644
index 00000000000..0932c8a95ea
--- /dev/null
+++ 
b/providers/keycloak/src/airflow/providers/keycloak/auth_manager/openapi/v2-keycloak-auth-manager-generated.yaml
@@ -0,0 +1,175 @@
+openapi: 3.1.0
+info:
+  title: Keycloak auth manager sub application
+  description: This is the Keycloak auth manager fastapi sub application. This 
API
+    is only available if the auth manager used in the Airflow environment is 
Keycloak
+    auth manager. This sub application provides login routes.
+  version: 0.1.0
+paths:
+  /auth/login:
+    get:
+      tags:
+      - KeycloakAuthManagerLogin
+      summary: Login
+      description: Initiate the authentication.
+      operationId: login
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema: {}
+  /auth/login_callback:
+    get:
+      tags:
+      - KeycloakAuthManagerLogin
+      summary: Login Callback
+      description: Authenticate the user.
+      operationId: login_callback
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema: {}
+  /auth/refresh:
+    get:
+      tags:
+      - KeycloakAuthManagerLogin
+      summary: Refresh
+      description: Refresh the token.
+      operationId: refresh
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema: {}
+      security:
+      - OAuth2PasswordBearer: []
+      - HTTPBearer: []
+  /auth/token:
+    post:
+      tags:
+      - KeycloakAuthManagerToken
+      summary: Create Token
+      operationId: create_token
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/TokenBody'
+        required: true
+      responses:
+        '201':
+          description: Successful Response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/TokenResponse'
+        '400':
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPExceptionResponse'
+        '401':
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPExceptionResponse'
+        '422':
+          description: Validation Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPValidationError'
+components:
+  schemas:
+    HTTPExceptionResponse:
+      properties:
+        detail:
+          anyOf:
+          - type: string
+          - additionalProperties: true
+            type: object
+          title: Detail
+      type: object
+      required:
+      - detail
+      title: HTTPExceptionResponse
+      description: HTTPException Model used for error response.
+    HTTPValidationError:
+      properties:
+        detail:
+          items:
+            $ref: '#/components/schemas/ValidationError'
+          type: array
+          title: Detail
+      type: object
+      title: HTTPValidationError
+    TokenBody:
+      properties:
+        username:
+          type: string
+          title: Username
+        password:
+          type: string
+          title: Password
+      additionalProperties: false
+      type: object
+      required:
+      - username
+      - password
+      title: TokenBody
+      description: Token serializer for post bodies.
+    TokenResponse:
+      properties:
+        access_token:
+          type: string
+          title: Access Token
+      type: object
+      required:
+      - access_token
+      title: TokenResponse
+      description: Token serializer for responses.
+    ValidationError:
+      properties:
+        loc:
+          items:
+            anyOf:
+            - type: string
+            - type: integer
+          type: array
+          title: Location
+        msg:
+          type: string
+          title: Message
+        type:
+          type: string
+          title: Error Type
+      type: object
+      required:
+      - loc
+      - msg
+      - type
+      title: ValidationError
+  securitySchemes:
+    OAuth2PasswordBearer:
+      type: oauth2
+      description: To authenticate Airflow API requests, clients must include 
a JWT
+        (JSON Web Token) in the Authorization header of each request. This 
token is
+        used to verify the identity of the client and ensure that they have 
the appropriate
+        permissions to access the requested resources. You can use the 
endpoint ``POST
+        /auth/token`` in order to generate a JWT token. Upon successful 
authentication,
+        the server will issue a JWT token that contains the necessary 
information
+        (such as user identity and scope) to authenticate subsequent requests. 
To
+        learn more about Airflow public API authentication, please read 
https://airflow.apache.org/docs/apache-airflow/stable/security/api.html.
+      flows:
+        password:
+          scopes: {}
+          tokenUrl: /auth/token
+    HTTPBearer:
+      type: http
+      scheme: bearer
diff --git a/scripts/ci/pre_commit/generate_openapi_spec_keycloak.py 
b/scripts/ci/pre_commit/generate_openapi_spec_keycloak.py
new file mode 100755
index 00000000000..35632cf7fb8
--- /dev/null
+++ b/scripts/ci/pre_commit/generate_openapi_spec_keycloak.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# 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 sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).parent.resolve()))
+from common_precommit_utils import (
+    initialize_breeze_precommit,
+    run_command_via_breeze_shell,
+    validate_cmd_result,
+)
+
+initialize_breeze_precommit(__name__, __file__)
+
+cmd_result = run_command_via_breeze_shell(
+    ["python3", 
"/opt/airflow/scripts/in_container/run_generate_openapi_spec_keycloak.py"],
+    backend="postgres",
+    skip_environment_initialization=False,
+)
+
+validate_cmd_result(cmd_result)
diff --git a/scripts/in_container/run_generate_openapi_spec_keycloak.py 
b/scripts/in_container/run_generate_openapi_spec_keycloak.py
new file mode 100755
index 00000000000..648bfa1b179
--- /dev/null
+++ b/scripts/in_container/run_generate_openapi_spec_keycloak.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# 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 sys
+from pathlib import Path
+
+from airflow.providers.keycloak.auth_manager import __file__ as 
KEYCLOAK_AUTH_MANAGER_PATH
+from airflow.providers.keycloak.auth_manager.keycloak_auth_manager import 
KeycloakAuthManager
+from airflow.providers_manager import ProvidersManager
+
+sys.path.insert(0, str(Path(__file__).parent.resolve()))
+from in_container_utils import console, generate_openapi_file, 
validate_openapi_file
+
+KEYCLOAK_AUTH_MANAGER_OPENAPI_SPEC_FILE = (
+    Path(KEYCLOAK_AUTH_MANAGER_PATH).parent / "openapi" / 
"v2-keycloak-auth-manager-generated.yaml"
+)
+
+ProvidersManager().initialize_providers_configuration()
+
+# Generate Keycloak auth manager openapi spec
+keycloak_auth_manager_app = KeycloakAuthManager().get_fastapi_app()
+if keycloak_auth_manager_app:
+    generate_openapi_file(
+        app=keycloak_auth_manager_app, 
file_path=KEYCLOAK_AUTH_MANAGER_OPENAPI_SPEC_FILE, prefix="/auth"
+    )
+    validate_openapi_file(KEYCLOAK_AUTH_MANAGER_OPENAPI_SPEC_FILE)
+else:
+    console.print("[red]Keycloak auth manager app not found. Skipping OpenAPI 
spec generation.[/]")
+    sys.exit(1)


Reply via email to