Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

D205 Support - WWW #33298

Merged
merged 2 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions airflow/www/api/experimental/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ def decorated(*args, **kwargs):

def add_deprecation_headers(response: Response):
"""
Add `Deprecation HTTP Header Field
<https://tools.ietf.org/id/draft-dalal-deprecation-header-03.html>`__.
Add Deprecation HTTP Header Field.

.. seealso:: IETF proposal for the header field
`here <https://datatracker.ietf.org/doc/draft-dalal-deprecation-header/>`_.
"""
response.headers["Deprecation"] = "true"
doc_url = get_docs_url("upgrading-to-2.html#migration-guide-from-experimental-api-to-stable-api-v1")
Expand All @@ -79,10 +81,7 @@ def add_deprecation_headers(response: Response):
@api_experimental.route("/dags/<string:dag_id>/dag_runs", methods=["POST"])
@requires_authentication
def trigger_dag(dag_id):
"""
Trigger a new dag run for a Dag with an execution date of now unless
specified in the data.
"""
"""Trigger a new dag run for a Dag with an execution date of now unless specified in the data."""
data = request.get_json(force=True)

run_id = None
Expand Down Expand Up @@ -251,6 +250,7 @@ def dag_is_paused(dag_id):
def task_instance_info(dag_id, execution_date, task_id):
"""
Returns a JSON with a task instance's public instance variables.

The format for the exec_date is expected to be
"YYYY-mm-DDTHH:MM:SS", for example: "2016-11-16T11:34:15". This will
of course need to have been encoded for URL in the request.
Expand Down Expand Up @@ -287,6 +287,7 @@ def task_instance_info(dag_id, execution_date, task_id):
def dag_run_status(dag_id, execution_date):
"""
Returns a JSON with a dag_run's public instance variables.

The format for the exec_date is expected to be
"YYYY-mm-DDTHH:MM:SS", for example: "2016-11-16T11:34:15". This will
of course need to have been encoded for URL in the request.
Expand Down
3 changes: 2 additions & 1 deletion airflow/www/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@

def _mask_variable_fields(extra_fields):
"""
Mask the 'val_content' field if 'key_content' is in the mask list.

The variable requests values and args comes in this form:
[('key', 'key_content'),('val', 'val_content'), ('description', 'description_content')]
So we need to mask the 'val_content' field if 'key_content' is in the mask list.
"""
result = []
keyname = None
Expand Down
12 changes: 5 additions & 7 deletions airflow/www/extensions/init_appbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def dynamic_class_import(class_path):
class AirflowAppBuilder:
"""
This is the base class for all the framework.

This is where you will register all your views
and create the menu structure.
Will hold your flask app object, all your views, and security classes.
Expand Down Expand Up @@ -235,10 +236,7 @@ def _init_extension(self, app):
app.extensions["appbuilder"] = self

def _swap_url_filter(self):
"""
Use our url filtering util function so there is consistency between
FAB and Airflow routes.
"""
"""Use our url filtering util function so there is consistency between FAB and Airflow routes."""
from flask_appbuilder.security import views as fab_sec_views

from airflow.www.views import get_safe_url
Expand Down Expand Up @@ -537,9 +535,9 @@ def add_separator(self, category, cond=None):

def add_view_no_menu(self, baseview, endpoint=None, static_folder=None):
"""
Add your views without creating a menu.
:param baseview:
A BaseView type class instantiated.
Add your views without creating a menu.

:param baseview: A BaseView type class instantiated.
"""
baseview = self._check_and_init(baseview)
log.info(LOGMSG_INF_FAB_ADD_VIEW.format(baseview.__class__.__name__, ""))
Expand Down
5 changes: 3 additions & 2 deletions airflow/www/extensions/init_dagbag.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@

def init_dagbag(app):
"""
Create global DagBag for webserver and API. To access it use
``flask.current_app.dag_bag``.
Create global DagBag for webserver and API.

To access it use ``flask.current_app.dag_bag``.
"""
if os.environ.get("SKIP_DAGS_PARSING") == "True":
app.dag_bag = DagBag(os.devnull, include_examples=False)
Expand Down
6 changes: 4 additions & 2 deletions airflow/www/extensions/init_robots.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@

def init_robots(app):
"""
Add X-Robots-Tag header. Use it to avoid search engines indexing airflow. This mitigates some
of the risk associated with exposing Airflow to the public internet, however it does not
Add X-Robots-Tag header.

Use it to avoid search engines indexing airflow. This mitigates some of the risk
associated with exposing Airflow to the public internet, however it does not
address the real security risks associated with such a deployment.

See also: https://developers.google.com/search/docs/advanced/robots/robots_meta_tag#xrobotstag
Expand Down
5 changes: 3 additions & 2 deletions airflow/www/extensions/init_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@

def init_xframe_protection(app):
"""
Add X-Frame-Options header. Use it to avoid click-jacking attacks, by ensuring that their content is not
embedded into other sites.
Add X-Frame-Options header.

Use it to avoid click-jacking attacks, by ensuring that their content is not embedded into other sites.

See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
"""
Expand Down
24 changes: 11 additions & 13 deletions airflow/www/fab_security/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,7 @@


def _oauth_tokengetter(token=None):
"""
Default function to return the current user oauth token
from session cookie.
"""
"""Default function to return the current user oauth token from session cookie."""
token = session.get("oauth")
log.debug("Token Get: %s", token)
return token
Expand Down Expand Up @@ -470,11 +467,10 @@ def current_user(self):

def oauth_user_info_getter(self, f):
"""
Decorator function to be the OAuth user info getter
for all the providers, receives provider and response
return a dict with the information returned from the provider.
The returned user info dict should have it's keys with the same
name as the User Model.
Decorator function to be the OAuth user info getter for all the providers.

Receives provider and response return a dict with the information returned from the provider.
The returned user info dict should have it's keys with the same name as the User Model.

Use it like this an example for GitHub ::

Expand All @@ -500,8 +496,9 @@ def wraps(provider, response=None):

def get_oauth_token_key_name(self, provider):
"""
Returns the token_key name for the oauth provider
if none is configured defaults to oauth_token
Returns the token_key name for the oauth provider.

If none is configured defaults to oauth_token
this is configured using OAUTH_PROVIDERS and token_key key.
"""
for _provider in self.oauth_providers:
Expand Down Expand Up @@ -1504,8 +1501,9 @@ def create_permission(self, action_name: str, resource_name: str) -> Permission

def delete_permission(self, action_name: str, resource_name: str) -> None:
"""
Deletes the permission linking an action->resource pair. Doesn't delete the
underlying action or resource.
Deletes the permission linking an action->resource pair.

Doesn't delete the underlying action or resource.

:param action_name: Name of existing action
:param resource_name: Name of existing resource
Expand Down
11 changes: 5 additions & 6 deletions airflow/www/fab_security/sqla/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@

class SecurityManager(BaseSecurityManager):
"""
Responsible for authentication, registering security views,
role and permission auto management.
Responsible for authentication, registering security views, role and permission auto management.

If you want to change anything just inherit and override, then
pass your own security manager to AppBuilder.
Expand Down Expand Up @@ -281,8 +280,7 @@ def permission_exists_in_one_or_more_roles(
self, resource_name: str, action_name: str, role_ids: list[int]
) -> bool:
"""
Method to efficiently check if a certain permission exists
on a list of role id's. This is used by `has_access`.
Efficiently check if a certain permission exists on a list of role ids; used by `has_access`.

:param resource_name: The view's name to check if exists on one of the roles
:param action_name: The permission name to check if exists
Expand Down Expand Up @@ -507,8 +505,9 @@ def create_permission(self, action_name, resource_name) -> Permission | None:

def delete_permission(self, action_name: str, resource_name: str) -> None:
"""
Deletes the permission linking an action->resource pair. Doesn't delete the
underlying action or resource.
Deletes the permission linking an action->resource pair.

Doesn't delete the underlying action or resource.

:param action_name: Name of existing action
:param resource_name: Name of existing resource
Expand Down
5 changes: 1 addition & 4 deletions airflow/www/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@ class DateTimeForm(FlaskForm):


class DateTimeWithNumRunsForm(FlaskForm):
"""
Date time and number of runs form for tree view, task duration
and landing times.
"""
"""Date time and number of runs form for tree view, task duration and landing times."""

base_date = DateTimeWithTimezoneField(
"Anchor date", widget=AirflowDateTimePickerWidget(), default=timezone.utcnow()
Expand Down
25 changes: 18 additions & 7 deletions airflow/www/security.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#
# 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
Expand Down Expand Up @@ -458,8 +457,9 @@ def is_dag_resource(self, resource_name: str) -> bool:

def has_access(self, action_name: str, resource_name: str, user=None) -> bool:
"""
Verify whether a given user could perform a certain action
(e.g can_read, can_write, can_delete) on the given resource.
Verify whether a given user could perform a certain action on the given resource.

Example actions might include can_read, can_write, can_delete, etc.

:param action_name: action_name on resource (e.g can_read, can_edit).
:param resource_name: name of view-menu or resource.
Expand Down Expand Up @@ -487,7 +487,8 @@ def _has_role(self, role_name_or_list: Container, user) -> bool:

def has_all_dags_access(self, user) -> bool:
"""
Has all the dag access in any of the 3 cases:
Has all the dag access in any of the 3 cases.

1. Role needs to be in (Admin, Viewer, User, Op).
2. Has can_read action on dags resource.
3. Has can_edit action on dags resource.
Expand Down Expand Up @@ -533,6 +534,7 @@ def clean_perms(self) -> None:
def _merge_perm(self, action_name: str, resource_name: str) -> None:
"""
Add the new (action, resource) to assoc_permission_role if it doesn't exist.

It will add the related entry to ab_permission and ab_resource two meta tables as well.

:param action_name: Name of the action
Expand Down Expand Up @@ -576,6 +578,8 @@ def get_all_permissions(self) -> set[tuple[str, str]]:

def _get_all_non_dag_permissions(self) -> dict[tuple[str, str], Permission]:
"""
Get permissions except those that are for specific DAGs.

Returns a dict with a key of (action_name, resource_name) and value of permission
with all permissions except those that are for specific DAGs.
"""
Expand All @@ -602,6 +606,8 @@ def _get_all_roles_with_permissions(self) -> dict[str, Role]:

def create_dag_specific_permissions(self) -> None:
"""
Add permissions to all DAGs.

Creates 'can_read', 'can_edit', and 'can_delete' permissions for all
DAGs, along with any `access_control` permissions provided in them.

Expand All @@ -627,7 +633,9 @@ def create_dag_specific_permissions(self) -> None:

def update_admin_permission(self) -> None:
"""
Admin should have all the permissions, except the dag permissions.
Add missing permissions to the table for admin.

Admin should get all the permissions, except the dag permissions
because Admin already has Dags permission.
Add the missing ones to the table for admin.

Expand All @@ -649,6 +657,8 @@ def update_admin_permission(self) -> None:

def sync_roles(self) -> None:
"""
Initialize default and custom roles with related permissions.

1. Init the default role(Admin, Viewer, User, Op, public)
with related permissions.
2. Init the custom role(dag-user) with related permissions.
Expand Down Expand Up @@ -681,8 +691,9 @@ def sync_perm_for_dag(
access_control: dict[str, Collection[str]] | None = None,
) -> None:
"""
Sync permissions for given dag id. The dag id surely exists in our dag bag
as only / refresh button or DagBag will call this function.
Sync permissions for given dag id.

The dag id surely exists in our dag bag as only / refresh button or DagBag will call this function.

:param dag_id: the ID of the DAG whose permissions should be updated
:param access_control: a dict where each key is a rolename and
Expand Down
25 changes: 13 additions & 12 deletions airflow/www/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,12 @@ def generate_pages(
sorting_direction=None,
):
"""
Generates the HTML for a paging component using a similar logic to the paging
auto-generated by Flask managed views. The paging component defines a number of
pages visible in the pager (window) and once the user goes to a page beyond the
largest visible, it would scroll to the right the page numbers and keeps the
current one in the middle of the pager component. When in the last pages,
Generates the HTML for a paging component.

Uses a similar logic to the paging auto-generated by Flask managed views. The paging
component defines a number of pages visible in the pager (window) and once the user
goes to a page beyond the largest visible, it would scroll to the right the page numbers
and keeps the current one in the middle of the pager component. When in the last pages,
the pages won't scroll and just keep moving until the last page. Pager also contains
<first, previous, ..., next, last> pages.
This component takes into account custom parameters such as search, status, and tags
Expand Down Expand Up @@ -646,10 +647,11 @@ def get_attr_renderer():

def get_chart_height(dag):
"""
We use the number of tasks in the DAG as a heuristic to
approximate the size of generated chart (otherwise the charts are tiny and unreadable
when DAGs have a large number of tasks). Ideally nvd3 should allow for dynamic-height
charts, that is charts that take up space based on the size of the components within.
Use the number of tasks in the DAG to approximate the size of generated chart.

Without this the charts are tiny and unreadable when DAGs have a large number of tasks).
Ideally nvd3 should allow for dynamic-height charts, that is charts that take up space
based on the size of the components within.
TODO(aoen): See [AIRFLOW-1263].
"""
return 600 + len(dag.tasks) * 10
Expand Down Expand Up @@ -787,10 +789,9 @@ def __init__(self, datamodel):

class CustomSQLAInterface(SQLAInterface):
"""
FAB does not know how to handle columns with leading underscores because
they are not supported by WTForm. This hack will remove the leading
'_' from the key to lookup the column names.
FAB does not know how to handle columns with leading underscores because they are not supported by WTForm.

This hack will remove the leading '_' from the key to lookup the column names.
"""

def __init__(self, obj, session: Session | None = None):
Expand Down
Loading