Skip to content

Commit

Permalink
Fix displaying dbt docs as menu item in Astro (#1280)
Browse files Browse the repository at this point in the history
As of Cosmos 1.7.0, Astro customers who have the role of "Organization
Member" or "Workspace Operator" are not able to see the menu item "dbt
Docs" under "Browser". They could access the `/cosmos/dbt_docs` view
directly, though. This PR solves this issue.

Closes: #1131 

# About the solution

This PR follows the Astro
[documentation](https://www.astronomer.io/docs/learn/using-airflow-plugins#flask-blueprints-and-appbuilder-views)
recommendation:
> If you want to use custom menu items in an Airflow environment hosted
on Astro, you must give your plugin the necessary permissions. To do
this, use the @has_access decorator to give your BaseView method
ACTION_CAN_ACCESS_MENU permissions.

By adding:
```
    @has_access([(permissions.ACTION_CAN_ACCESS_MENU, "Custom Menu")]) 
```

# How the fix was tested

In addition to the unit test that checks if all Cosmos plugin endpoints
have the expected permissions, I also validated this change with a user
with the "Organization Member" role. I confirmed that they could see the
menu item and that it redirects to the correct page:

![Screenshot 2024-10-24 at 10 24
03](https://github.com/user-attachments/assets/ea835086-36b6-438f-9e2c-9696cdda8b81)
  • Loading branch information
tatiana authored Oct 24, 2024
1 parent 7915fd0 commit 342ce3a
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 9 deletions.
14 changes: 11 additions & 3 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
Changelog
=========

1.7.1a1 (2024-10-24)

1.7.1a3 (2024-10-24)
--------------------

Others
Bug fixes

* Fix displaying dbt docs as menu item in Astro by @tatiana in #1280

* Fix release to PyPI after rst directive disabled by @tatiana in #1281
Docs

* Update the URL for sample dbt docs hosted in Astronomer S3 bucket by @pankajkoti in #1283

Others

* Fix release after the raw rst directive disabled was disabled in PyPI by @tatiana in #1282


1.7.0 (2024-10-04)
Expand Down
2 changes: 1 addition & 1 deletion cosmos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Contains dags, task groups, and operators.
"""

__version__ = "1.7.1a1"
__version__ = "1.7.1a3"


from cosmos.airflow.dag import DbtDag
Expand Down
37 changes: 32 additions & 5 deletions cosmos/plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, Dict, Optional, Tuple
from urllib.parse import urlsplit

from airflow.configuration import conf
from airflow.plugins_manager import AirflowPlugin
from airflow.security import permissions
from airflow.www.auth import has_access
Expand Down Expand Up @@ -200,14 +201,24 @@ def create_blueprint(
return super().create_blueprint(appbuilder, endpoint=endpoint, static_folder=self.static_folder) # type: ignore[no-any-return]

@expose("/dbt_docs") # type: ignore[misc]
@has_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE)])
@has_access(
[
(permissions.ACTION_CAN_ACCESS_MENU, "Custom Menu"),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
]
)
def dbt_docs(self) -> str:
if dbt_docs_dir is None:
return self.render_template("dbt_docs_not_set_up.html") # type: ignore[no-any-return,no-untyped-call]
return self.render_template("dbt_docs.html") # type: ignore[no-any-return,no-untyped-call]

@expose("/dbt_docs_index.html") # type: ignore[misc]
@has_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE)])
@has_access(
[
(permissions.ACTION_CAN_ACCESS_MENU, "Custom Menu"),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
]
)
def dbt_docs_index(self) -> Tuple[str, int, Dict[str, Any]]:
if dbt_docs_dir is None:
abort(404)
Expand All @@ -222,7 +233,12 @@ def dbt_docs_index(self) -> Tuple[str, int, Dict[str, Any]]:
return html, 200, {"Content-Security-Policy": "frame-ancestors 'self'"}

@expose("/catalog.json") # type: ignore[misc]
@has_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE)])
@has_access(
[
(permissions.ACTION_CAN_ACCESS_MENU, "Custom Menu"),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
]
)
def catalog(self) -> Tuple[str, int, Dict[str, Any]]:
if dbt_docs_dir is None:
abort(404)
Expand All @@ -234,7 +250,12 @@ def catalog(self) -> Tuple[str, int, Dict[str, Any]]:
return data, 200, {"Content-Type": "application/json"}

@expose("/manifest.json") # type: ignore[misc]
@has_access([(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE)])
@has_access(
[
(permissions.ACTION_CAN_ACCESS_MENU, "Custom Menu"),
(permissions.ACTION_CAN_READ, permissions.RESOURCE_WEBSITE),
]
)
def manifest(self) -> Tuple[str, int, Dict[str, Any]]:
if dbt_docs_dir is None:
abort(404)
Expand All @@ -251,4 +272,10 @@ def manifest(self) -> Tuple[str, int, Dict[str, Any]]:

class CosmosPlugin(AirflowPlugin):
name = "cosmos"
appbuilder_views = [{"name": "dbt Docs", "category": "Browse", "view": dbt_docs_view}]
item = {
"name": "dbt Docs",
"category": "Browse",
"view": dbt_docs_view,
"href": conf.get("webserver", "base_url") + "/cosmos/dbt_docs",
}
appbuilder_views = [item]
11 changes: 11 additions & 0 deletions tests/plugin/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,14 @@ def test_open_file_local(mock_file):
res = open_file("/my/path")
mock_file.assert_called_with("/my/path")
assert res == "mock file contents"


@pytest.mark.integration
@pytest.mark.parametrize(
"url_path", ["/cosmos/dbt_docs", "/cosmos/dbt_docs_index.html", "/cosmos/catalog.json", "/cosmos/manifest.json"]
)
def test_has_access_with_permissions(url_path, app):
dbt_docs_view.appbuilder.sm.check_authorization = MagicMock()
mock_check_auth = dbt_docs_view.appbuilder.sm.check_authorization
app.get(url_path)
assert mock_check_auth.call_args[0][0] == [("menu_access", "Custom Menu"), ("can_read", "Website")]

0 comments on commit 342ce3a

Please sign in to comment.