apache-airflow-providers-fab 2.0.2rc2__py3-none-any.whl → 2.1.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. airflow/providers/fab/__init__.py +3 -3
  2. airflow/providers/fab/auth_manager/api_endpoints/role_and_permission_endpoint.py +4 -0
  3. airflow/providers/fab/auth_manager/api_endpoints/user_endpoint.py +4 -0
  4. airflow/providers/fab/auth_manager/models/__init__.py +56 -6
  5. airflow/providers/fab/auth_manager/models/anonymous_user.py +5 -1
  6. airflow/providers/fab/auth_manager/security_manager/override.py +25 -21
  7. airflow/providers/fab/get_provider_info.py +56 -0
  8. airflow/providers/fab/www/app.py +2 -0
  9. airflow/providers/fab/www/auth.py +3 -3
  10. airflow/providers/fab/www/extensions/init_jinja_globals.py +1 -1
  11. airflow/providers/fab/www/extensions/init_wsgi_middlewares.py +41 -0
  12. airflow/providers/fab/www/package-lock.json +1258 -520
  13. airflow/providers/fab/www/package.json +7 -7
  14. airflow/providers/fab/www/static/dist/{main.edb2d40dfbbc537916e3.js → main.eb83be09d97c23018bcb.js} +1 -1
  15. airflow/providers/fab/www/static/dist/manifest.json +11 -11
  16. airflow/providers/fab/www/static/dist/{moment.624b1f00ba723d39ce06.js → moment.75a9286ff6019fefe5d9.js} +1 -1
  17. {apache_airflow_providers_fab-2.0.2rc2.dist-info → apache_airflow_providers_fab-2.1.0rc1.dist-info}/METADATA +10 -10
  18. {apache_airflow_providers_fab-2.0.2rc2.dist-info → apache_airflow_providers_fab-2.1.0rc1.dist-info}/RECORD +33 -32
  19. /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.feec4a4075c2f3d6ae01.css → airflowDefaultTheme.a26736fa84b3356edac0.css} +0 -0
  20. /airflow/providers/fab/www/static/dist/{airflowDefaultTheme.feec4a4075c2f3d6ae01.js → airflowDefaultTheme.a26736fa84b3356edac0.js} +0 -0
  21. /airflow/providers/fab/www/static/dist/{flash.137b30cff85b5588e661.css → flash.fbcc531a39479aa27065.css} +0 -0
  22. /airflow/providers/fab/www/static/dist/{flash.137b30cff85b5588e661.js → flash.fbcc531a39479aa27065.js} +0 -0
  23. /airflow/providers/fab/www/static/dist/{loadingDots.48ab7d5b04e66f2686b0.css → loadingDots.e1fc82c3ac3f9af3771e.css} +0 -0
  24. /airflow/providers/fab/www/static/dist/{loadingDots.48ab7d5b04e66f2686b0.js → loadingDots.e1fc82c3ac3f9af3771e.js} +0 -0
  25. /airflow/providers/fab/www/static/dist/{main.edb2d40dfbbc537916e3.css → main.eb83be09d97c23018bcb.css} +0 -0
  26. /airflow/providers/fab/www/static/dist/{main.edb2d40dfbbc537916e3.js.LICENSE.txt → main.eb83be09d97c23018bcb.js.LICENSE.txt} +0 -0
  27. /airflow/providers/fab/www/static/dist/{materialIcons.57390fa60d8f61175334.css → materialIcons.b21138ea09d0cdf9ffc4.css} +0 -0
  28. /airflow/providers/fab/www/static/dist/{materialIcons.57390fa60d8f61175334.js → materialIcons.b21138ea09d0cdf9ffc4.js} +0 -0
  29. /airflow/providers/fab/www/static/dist/{moment.624b1f00ba723d39ce06.js.LICENSE.txt → moment.75a9286ff6019fefe5d9.js.LICENSE.txt} +0 -0
  30. {apache_airflow_providers_fab-2.0.2rc2.dist-info → apache_airflow_providers_fab-2.1.0rc1.dist-info}/WHEEL +0 -0
  31. {apache_airflow_providers_fab-2.0.2rc2.dist-info → apache_airflow_providers_fab-2.1.0rc1.dist-info}/entry_points.txt +0 -0
  32. {apache_airflow_providers_fab-2.0.2rc2.dist-info → apache_airflow_providers_fab-2.1.0rc1.dist-info}/licenses/3rd-party-licenses/LICENSES-ui.txt +0 -0
  33. {apache_airflow_providers_fab-2.0.2rc2.dist-info → apache_airflow_providers_fab-2.1.0rc1.dist-info}/licenses/NOTICE +0 -0
@@ -29,11 +29,11 @@ from airflow import __version__ as airflow_version
29
29
 
30
30
  __all__ = ["__version__"]
31
31
 
32
- __version__ = "2.0.2"
32
+ __version__ = "2.1.0"
33
33
 
34
34
  if packaging.version.parse(packaging.version.parse(airflow_version).base_version) < packaging.version.parse(
35
- "3.0.0"
35
+ "3.0.2"
36
36
  ):
37
37
  raise RuntimeError(
38
- f"The package `apache-airflow-providers-fab:{__version__}` needs Apache Airflow 3.0.0+"
38
+ f"The package `apache-airflow-providers-fab:{__version__}` needs Apache Airflow 3.0.2+"
39
39
  )
@@ -123,6 +123,8 @@ def patch_role(*, role_name: str, update_mask: UpdateMask = None) -> APIResponse
123
123
  """Update a role."""
124
124
  security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
125
125
  body = request.json
126
+ if body is None:
127
+ raise BadRequest("Request body is required")
126
128
  try:
127
129
  data = role_schema.load(body)
128
130
  except ValidationError as err:
@@ -156,6 +158,8 @@ def post_role() -> APIResponse:
156
158
  """Create a new role."""
157
159
  security_manager = cast("FabAuthManager", get_auth_manager()).security_manager
158
160
  body = request.json
161
+ if body is None:
162
+ raise BadRequest("Request body is required")
159
163
  try:
160
164
  data = role_schema.load(body)
161
165
  except ValidationError as err:
@@ -88,6 +88,8 @@ def get_users(*, limit: int, order_by: str = "id", offset: str | None = None) ->
88
88
  @requires_access_custom_view("POST", permissions.RESOURCE_USER)
89
89
  def post_user() -> APIResponse:
90
90
  """Create a new user."""
91
+ if request.json is None:
92
+ raise BadRequest("Request body is required")
91
93
  try:
92
94
  data = user_schema.load(request.json)
93
95
  except ValidationError as e:
@@ -131,6 +133,8 @@ def post_user() -> APIResponse:
131
133
  @requires_access_custom_view("PUT", permissions.RESOURCE_USER)
132
134
  def patch_user(*, username: str, update_mask: UpdateMask = None) -> APIResponse:
133
135
  """Update a user."""
136
+ if request.json is None:
137
+ raise BadRequest("Request body is required")
134
138
  try:
135
139
  data = user_schema.load(request.json)
136
140
  except ValidationError as e:
@@ -102,11 +102,37 @@ assoc_permission_role = Table(
102
102
  "ab_permission_view_role",
103
103
  Model.metadata,
104
104
  Column("id", Integer, primary_key=True),
105
- Column("permission_view_id", Integer, ForeignKey("ab_permission_view.id")),
106
- Column("role_id", Integer, ForeignKey("ab_role.id")),
105
+ Column(
106
+ "permission_view_id",
107
+ Integer,
108
+ ForeignKey("ab_permission_view.id", ondelete="CASCADE"),
109
+ ),
110
+ Column("role_id", Integer, ForeignKey("ab_role.id", ondelete="CASCADE")),
107
111
  UniqueConstraint("permission_view_id", "role_id"),
108
112
  )
109
113
 
114
+ assoc_user_group = Table(
115
+ "ab_user_group",
116
+ Model.metadata,
117
+ Column("id", Integer, primary_key=True),
118
+ Column("user_id", Integer, ForeignKey("ab_user.id", ondelete="CASCADE")),
119
+ Column("group_id", Integer, ForeignKey("ab_group.id", ondelete="CASCADE")),
120
+ UniqueConstraint("user_id", "group_id"),
121
+ Index("idx_user_id", "user_id"),
122
+ Index("idx_user_group_id", "group_id"),
123
+ )
124
+
125
+ assoc_group_role = Table(
126
+ "ab_group_role",
127
+ Model.metadata,
128
+ Column("id", Integer, primary_key=True),
129
+ Column("group_id", Integer, ForeignKey("ab_group.id", ondelete="CASCADE")),
130
+ Column("role_id", Integer, ForeignKey("ab_role.id", ondelete="CASCADE")),
131
+ UniqueConstraint("group_id", "role_id"),
132
+ Index("idx_group_id", "group_id"),
133
+ Index("idx_group_role_id", "role_id"),
134
+ )
135
+
110
136
 
111
137
  class Role(Model):
112
138
  """Represents a user role to which permissions can be assigned."""
@@ -115,7 +141,29 @@ class Role(Model):
115
141
 
116
142
  id = Column(Integer, primary_key=True)
117
143
  name = Column(String(64), unique=True, nullable=False)
118
- permissions = relationship("Permission", secondary=assoc_permission_role, backref="role", lazy="joined")
144
+ permissions = relationship(
145
+ "Permission",
146
+ secondary=assoc_permission_role,
147
+ backref="role",
148
+ lazy="joined",
149
+ passive_deletes=True,
150
+ )
151
+
152
+ def __repr__(self):
153
+ return self.name
154
+
155
+
156
+ class Group(Model):
157
+ """Represents a user group."""
158
+
159
+ __tablename__ = "ab_group"
160
+
161
+ id = Column(Integer, primary_key=True)
162
+ name = Column(String(100), unique=True, nullable=False)
163
+ label = Column(String(150))
164
+ description = Column(String(512))
165
+ users = relationship("User", secondary=assoc_user_group, backref="groups", passive_deletes=True)
166
+ roles = relationship("Role", secondary=assoc_group_role, backref="groups", passive_deletes=True)
119
167
 
120
168
  def __repr__(self):
121
169
  return self.name
@@ -148,8 +196,8 @@ assoc_user_role = Table(
148
196
  "ab_user_role",
149
197
  Model.metadata,
150
198
  Column("id", Integer, primary_key=True),
151
- Column("user_id", Integer, ForeignKey("ab_user.id")),
152
- Column("role_id", Integer, ForeignKey("ab_role.id")),
199
+ Column("user_id", Integer, ForeignKey("ab_user.id", ondelete="CASCADE")),
200
+ Column("role_id", Integer, ForeignKey("ab_role.id", ondelete="CASCADE")),
153
201
  UniqueConstraint("user_id", "role_id"),
154
202
  )
155
203
 
@@ -170,7 +218,9 @@ class User(Model, BaseUser):
170
218
  last_login = Column(DateTime)
171
219
  login_count = Column(Integer)
172
220
  fail_login_count = Column(Integer)
173
- roles = relationship("Role", secondary=assoc_user_role, backref="user", lazy="selectin")
221
+ roles = relationship(
222
+ "Role", secondary=assoc_user_role, backref="user", lazy="selectin", passive_deletes=True
223
+ )
174
224
  created_on = Column(DateTime, default=datetime.datetime.now, nullable=True)
175
225
  changed_on = Column(DateTime, default=datetime.datetime.now, nullable=True)
176
226
 
@@ -37,13 +37,17 @@ class AnonymousUser(AnonymousUserMixin, BaseUser):
37
37
  if not self._roles:
38
38
  public_role = current_app.config.get("AUTH_ROLE_PUBLIC", None)
39
39
  self._roles = {current_app.appbuilder.sm.find_role(public_role)} if public_role else set()
40
- return self._roles
40
+ return list(self._roles)
41
41
 
42
42
  @roles.setter
43
43
  def roles(self, roles):
44
44
  self._roles = roles
45
45
  self._perms = set()
46
46
 
47
+ @property
48
+ def groups(self):
49
+ return []
50
+
47
51
  @property
48
52
  def perms(self):
49
53
  if not self._perms:
@@ -55,6 +55,7 @@ from flask_appbuilder.security.views import (
55
55
  AuthOIDView,
56
56
  AuthRemoteUserView,
57
57
  RegisterUserModelView,
58
+ UserGroupModelView,
58
59
  )
59
60
  from flask_babel import lazy_gettext
60
61
  from flask_jwt_extended import JWTManager
@@ -71,6 +72,7 @@ from airflow.exceptions import AirflowException
71
72
  from airflow.models import DagBag
72
73
  from airflow.providers.fab.auth_manager.models import (
73
74
  Action,
75
+ Group,
74
76
  Permission,
75
77
  RegisterUser,
76
78
  Resource,
@@ -100,10 +102,7 @@ from airflow.providers.fab.auth_manager.views.user_edit import (
100
102
  from airflow.providers.fab.auth_manager.views.user_stats import CustomUserStatsChartView
101
103
  from airflow.providers.fab.www.security import permissions
102
104
  from airflow.providers.fab.www.security_manager import AirflowSecurityManagerV2
103
- from airflow.providers.fab.www.session import (
104
- AirflowDatabaseSessionInterface,
105
- AirflowDatabaseSessionInterface as FabAirflowDatabaseSessionInterface,
106
- )
105
+ from airflow.providers.fab.www.session import AirflowDatabaseSessionInterface
107
106
  from airflow.security.permissions import RESOURCE_BACKFILL
108
107
 
109
108
  if TYPE_CHECKING:
@@ -149,6 +148,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
149
148
  """ Models """
150
149
  user_model = User
151
150
  role_model = Role
151
+ group_model = Group
152
152
  action_model = Action
153
153
  resource_model = Resource
154
154
  permission_model = Permission
@@ -173,6 +173,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
173
173
  actionmodelview = ActionModelView
174
174
  permissionmodelview = PermissionPairModelView
175
175
  rolemodelview = CustomRoleModelView
176
+ groupmodelview = UserGroupModelView
176
177
  registeruser_model = RegisterUser
177
178
  registerusermodelview = RegisterUserModelView
178
179
  resourcemodelview = ResourceModelView
@@ -450,7 +451,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
450
451
  role_view = self.appbuilder.add_view(
451
452
  self.rolemodelview,
452
453
  "List Roles",
453
- icon="fa-group",
454
+ icon="fa-user-gear",
454
455
  label=lazy_gettext("List Roles"),
455
456
  category="Security",
456
457
  category_icon="fa-cogs",
@@ -532,12 +533,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
532
533
  return self.update_user(user)
533
534
 
534
535
  def reset_user_sessions(self, user: User) -> None:
535
- if isinstance(
536
- self.appbuilder.get_app.session_interface, AirflowDatabaseSessionInterface
537
- ) or isinstance(
538
- self.appbuilder.get_app.session_interface,
539
- FabAirflowDatabaseSessionInterface,
540
- ):
536
+ if isinstance(self.appbuilder.get_app.session_interface, AirflowDatabaseSessionInterface):
541
537
  interface = self.appbuilder.get_app.session_interface
542
538
  session = interface.db.session
543
539
  user_session_model = interface.sql_session_model
@@ -859,6 +855,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
859
855
  self.registerusermodelview.datamodel = SQLAInterface(self.registeruser_model)
860
856
 
861
857
  self.rolemodelview.datamodel = SQLAInterface(self.role_model)
858
+ self.groupmodelview.datamodel = SQLAInterface(self.group_model)
862
859
  self.actionmodelview.datamodel = SQLAInterface(self.action_model)
863
860
  self.resourcemodelview.datamodel = SQLAInterface(self.resource_model)
864
861
  self.permissionmodelview.datamodel = SQLAInterface(self.permission_model)
@@ -875,7 +872,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
875
872
  try:
876
873
  engine = self.get_session.get_bind(mapper=None, clause=None)
877
874
  inspector = inspect(engine)
878
- if "ab_user" not in inspector.get_table_names():
875
+ existing_tables = inspector.get_table_names()
876
+ if "ab_user" not in existing_tables or "ab_group" not in existing_tables:
879
877
  log.info(const.LOGMSG_INF_SEC_NO_DB)
880
878
  Base.metadata.create_all(engine)
881
879
  log.info(const.LOGMSG_INF_SEC_ADD_DB)
@@ -1311,15 +1309,20 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1311
1309
 
1312
1310
  def add_user(
1313
1311
  self,
1314
- username,
1315
- first_name,
1316
- last_name,
1317
- email,
1318
- role,
1319
- password="",
1320
- hashed_password="",
1312
+ username: str,
1313
+ first_name: str,
1314
+ last_name: str,
1315
+ email: str,
1316
+ role: list[Role] | Role | None = None,
1317
+ password: str = "",
1318
+ hashed_password: str = "",
1319
+ groups: list[Group] | None = None,
1321
1320
  ):
1322
1321
  """Create a user."""
1322
+ roles: list[Role] = []
1323
+ if role:
1324
+ roles = role if isinstance(role, list) else [role]
1325
+
1323
1326
  try:
1324
1327
  user = self.user_model()
1325
1328
  user.first_name = first_name
@@ -1328,7 +1331,8 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1328
1331
  user.email = email
1329
1332
  user.active = True
1330
1333
  self.get_session.add(user)
1331
- user.roles = role if isinstance(role, list) else [role]
1334
+ user.roles = roles
1335
+ user.groups = groups or []
1332
1336
  if hashed_password:
1333
1337
  user.password = hashed_password
1334
1338
  else:
@@ -1692,7 +1696,7 @@ class FabAirflowSecurityManagerOverride(AirflowSecurityManagerV2):
1692
1696
  """
1693
1697
  if user is None:
1694
1698
  user = g.user
1695
- return user.roles
1699
+ return user.roles + [role for group in user.groups for role in group.roles]
1696
1700
 
1697
1701
  """
1698
1702
  --------------------
@@ -30,6 +30,20 @@ def get_provider_info():
30
30
  "fab": {
31
31
  "description": "This section contains configs specific to FAB provider.",
32
32
  "options": {
33
+ "access_denied_message": {
34
+ "description": "The message displayed when a user attempts to execute actions beyond their authorised privileges.\n",
35
+ "version_added": "2.1.0",
36
+ "type": "string",
37
+ "example": None,
38
+ "default": "Access is Denied",
39
+ },
40
+ "expose_hostname": {
41
+ "description": "Expose hostname in the web server\n",
42
+ "version_added": "2.1.0",
43
+ "type": "string",
44
+ "example": None,
45
+ "default": "False",
46
+ },
33
47
  "auth_rate_limited": {
34
48
  "description": "Boolean for enabling rate limiting on authentication endpoints.\n",
35
49
  "version_added": "1.0.2",
@@ -79,6 +93,48 @@ def get_provider_info():
79
93
  "example": None,
80
94
  "default": "43200",
81
95
  },
96
+ "enable_proxy_fix": {
97
+ "description": "Enable werkzeug ``ProxyFix`` middleware for reverse proxy\n",
98
+ "version_added": "2.1.0",
99
+ "type": "boolean",
100
+ "example": None,
101
+ "default": "False",
102
+ },
103
+ "proxy_fix_x_for": {
104
+ "description": "Number of values to trust for ``X-Forwarded-For``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n<https://werkzeug.palletsprojects.com/en/2.3.x/middleware/proxy_fix/>`__ for more details.\n",
105
+ "version_added": "2.1.0",
106
+ "type": "integer",
107
+ "example": None,
108
+ "default": "1",
109
+ },
110
+ "proxy_fix_x_proto": {
111
+ "description": "Number of values to trust for ``X-Forwarded-Proto``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n<https://werkzeug.palletsprojects.com/en/2.3.x/middleware/proxy_fix/>`__ for more details.\n",
112
+ "version_added": "2.1.0",
113
+ "type": "integer",
114
+ "example": None,
115
+ "default": "1",
116
+ },
117
+ "proxy_fix_x_host": {
118
+ "description": "Number of values to trust for ``X-Forwarded-Host``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n<https://werkzeug.palletsprojects.com/en/2.3.x/middleware/proxy_fix/>`__ for more details.\n",
119
+ "version_added": "2.1.0",
120
+ "type": "integer",
121
+ "example": None,
122
+ "default": "1",
123
+ },
124
+ "proxy_fix_x_port": {
125
+ "description": "Number of values to trust for ``X-Forwarded-Port``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n<https://werkzeug.palletsprojects.com/en/2.3.x/middleware/proxy_fix/>`__ for more details.\n",
126
+ "version_added": "2.1.0",
127
+ "type": "integer",
128
+ "example": None,
129
+ "default": "1",
130
+ },
131
+ "proxy_fix_x_prefix": {
132
+ "description": "Number of values to trust for ``X-Forwarded-Prefix``.\nSee `Werkzeug: X-Forwarded-For Proxy Fix\n<https://werkzeug.palletsprojects.com/en/2.3.x/middleware/proxy_fix/>`__ for more details.\n",
133
+ "version_added": "2.1.0",
134
+ "type": "integer",
135
+ "example": None,
136
+ "default": "1",
137
+ },
82
138
  },
83
139
  }
84
140
  },
@@ -41,6 +41,7 @@ from airflow.providers.fab.www.extensions.init_views import (
41
41
  init_error_handlers,
42
42
  init_plugins,
43
43
  )
44
+ from airflow.providers.fab.www.extensions.init_wsgi_middlewares import init_wsgi_middleware
44
45
  from airflow.providers.fab.www.utils import get_session_lifetime_config
45
46
 
46
47
  app: Flask | None = None
@@ -103,6 +104,7 @@ def create_app(enable_plugins: bool):
103
104
  init_jinja_globals(flask_app, enable_plugins=enable_plugins)
104
105
  init_xframe_protection(flask_app)
105
106
  init_airflow_session_interface(flask_app)
107
+ init_wsgi_middleware(flask_app)
106
108
  return flask_app
107
109
 
108
110
 
@@ -61,7 +61,7 @@ log = logging.getLogger(__name__)
61
61
 
62
62
 
63
63
  def get_access_denied_message():
64
- return conf.get("webserver", "access_denied_message")
64
+ return conf.get("fab", "access_denied_message")
65
65
 
66
66
 
67
67
  def has_access_with_pk(f):
@@ -145,7 +145,7 @@ def _has_access(*, is_authorized: bool, func: Callable, args, kwargs):
145
145
  return (
146
146
  render_template(
147
147
  "airflow/no_roles_permissions.html",
148
- hostname=get_hostname() if conf.getboolean("webserver", "EXPOSE_HOSTNAME") else "",
148
+ hostname=get_hostname() if conf.getboolean("fab", "EXPOSE_HOSTNAME") else "",
149
149
  logout_url=get_fab_auth_manager().get_url_logout(),
150
150
  ),
151
151
  403,
@@ -217,7 +217,7 @@ def has_access_dag(method: ResourceMethod, access_entity: DagAccessEntity | None
217
217
  return (
218
218
  render_template(
219
219
  "airflow/no_roles_permissions.html",
220
- hostname=get_hostname() if conf.getboolean("webserver", "EXPOSE_HOSTNAME") else "",
220
+ hostname=get_hostname() if conf.getboolean("fab", "EXPOSE_HOSTNAME") else "",
221
221
  logout_url=get_auth_manager().get_url_logout(),
222
222
  ),
223
223
  403,
@@ -37,7 +37,7 @@ def init_jinja_globals(app, enable_plugins: bool):
37
37
  elif server_timezone == "utc":
38
38
  server_timezone = "UTC"
39
39
 
40
- expose_hostname = conf.getboolean("webserver", "EXPOSE_HOSTNAME")
40
+ expose_hostname = conf.getboolean("fab", "EXPOSE_HOSTNAME")
41
41
  hostname = get_hostname() if expose_hostname else "redact"
42
42
 
43
43
  try:
@@ -0,0 +1,41 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one
2
+ # or more contributor license agreements. See the NOTICE file
3
+ # distributed with this work for additional information
4
+ # regarding copyright ownership. The ASF licenses this file
5
+ # to you under the Apache License, Version 2.0 (the
6
+ # "License"); you may not use this file except in compliance
7
+ # with the License. You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import TYPE_CHECKING
21
+
22
+ from werkzeug.middleware.proxy_fix import ProxyFix
23
+
24
+ from airflow.configuration import conf
25
+
26
+ if TYPE_CHECKING:
27
+ from flask import Flask
28
+
29
+
30
+ def init_wsgi_middleware(flask_app: Flask) -> None:
31
+ """Handle X-Forwarded-* headers and base_url support."""
32
+ # Apply ProxyFix middleware
33
+ if conf.getboolean("fab", "ENABLE_PROXY_FIX"):
34
+ flask_app.wsgi_app = ProxyFix( # type: ignore
35
+ flask_app.wsgi_app,
36
+ x_for=conf.getint("fab", "PROXY_FIX_X_FOR", fallback=1),
37
+ x_proto=conf.getint("fab", "PROXY_FIX_X_PROTO", fallback=1),
38
+ x_host=conf.getint("fab", "PROXY_FIX_X_HOST", fallback=1),
39
+ x_port=conf.getint("fab", "PROXY_FIX_X_PORT", fallback=1),
40
+ x_prefix=conf.getint("fab", "PROXY_FIX_X_PREFIX", fallback=1),
41
+ )