From 312cbf736ce45e4292f9742bc58d81ec499991ba Mon Sep 17 00:00:00 2001 From: Amit Miran <47772523+amitmiran137@users.noreply.github.com> Date: Mon, 15 Feb 2021 10:57:37 +0200 Subject: [PATCH] feat(dashboard_rbac): add support for related roles (#13035) --- superset/dashboards/api.py | 5 ++++- superset/dashboards/filters.py | 32 ++++++++++++++++++++++++------ tests/dashboards/api_tests.py | 36 +++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/superset/dashboards/api.py b/superset/dashboards/api.py index dbde513d3a..9d148585fe 100644 --- a/superset/dashboards/api.py +++ b/superset/dashboards/api.py @@ -54,6 +54,7 @@ from superset.dashboards.filters import ( DashboardFavoriteFilter, DashboardFilter, DashboardTitleOrSlugFilter, + FilterRelatedRoles, ) from superset.dashboards.schemas import ( DashboardPostSchema, @@ -192,12 +193,14 @@ class DashboardRestApi(BaseSupersetModelRestApi): order_rel_fields = { "slices": ("slice_name", "asc"), "owners": ("first_name", "asc"), + "roles": ("name", "asc"), } related_field_filters = { "owners": RelatedFieldFilter("first_name", FilterRelatedOwners), + "roles": RelatedFieldFilter("name", FilterRelatedRoles), "created_by": RelatedFieldFilter("first_name", FilterRelatedOwners), } - allowed_rel_fields = {"owners", "created_by"} + allowed_rel_fields = {"owners", "roles", "created_by"} openapi_spec_tag = "Dashboards" """ Override the name set for this collection of endpoints """ diff --git a/superset/dashboards/filters.py b/superset/dashboards/filters.py index 7ca466e917..d7216ce919 100644 --- a/superset/dashboards/filters.py +++ b/superset/dashboards/filters.py @@ -14,7 +14,9 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from typing import Any + +# pylint: disable=too-few-public-methods +from typing import Any, Optional from flask_appbuilder.security.sqla.models import Role from flask_babel import lazy_gettext as _ @@ -29,7 +31,7 @@ from superset.views.base import BaseFilter, get_user_roles, is_user_admin from superset.views.base_api import BaseFavoriteFilter -class DashboardTitleOrSlugFilter(BaseFilter): # pylint: disable=too-few-public-methods +class DashboardTitleOrSlugFilter(BaseFilter): name = _("Title or Slug") arg_name = "title_or_slug" @@ -45,9 +47,7 @@ class DashboardTitleOrSlugFilter(BaseFilter): # pylint: disable=too-few-public- ) -class DashboardFavoriteFilter( - BaseFavoriteFilter -): # pylint: disable=too-few-public-methods +class DashboardFavoriteFilter(BaseFavoriteFilter): """ Custom filter for the GET list that filters all dashboards that a user has favored """ @@ -57,7 +57,7 @@ class DashboardFavoriteFilter( model = Dashboard -class DashboardFilter(BaseFilter): # pylint: disable=too-few-public-methods +class DashboardFilter(BaseFilter): """ List dashboards with the following criteria: 1. Those which the user owns @@ -138,3 +138,23 @@ class DashboardFilter(BaseFilter): # pylint: disable=too-few-public-methods ) return query + + +class FilterRelatedRoles(BaseFilter): + """ + A filter to allow searching for related roles of a resource. + + Use in the api by adding something like: + related_field_filters = { + "roles": RelatedFieldFilter("name", FilterRelatedRoles), + } + """ + + name = _("Role") + arg_name = "roles" + + def apply(self, query: Query, value: Optional[Any]) -> Query: + role_model = security_manager.role_model + if value: + return query.filter(role_model.name.ilike(f"%{value}%"),) + return query diff --git a/tests/dashboards/api_tests.py b/tests/dashboards/api_tests.py index 641154e1c2..658963ed52 100644 --- a/tests/dashboards/api_tests.py +++ b/tests/dashboards/api_tests.py @@ -29,7 +29,7 @@ import yaml from sqlalchemy.sql import func from freezegun import freeze_time -from sqlalchemy import and_, or_ +from sqlalchemy import and_ from superset import db, security_manager from superset.models.dashboard import Dashboard from superset.models.core import FavStar, FavStarClassName @@ -1343,3 +1343,37 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin): assert response == { "message": {"metadata.yaml": {"type": ["Must be equal to Dashboard."]}} } + + def test_get_all_related_roles(self): + """ + API: Test get filter related roles + """ + self.login(username="admin") + uri = f"api/v1/dashboard/related/roles" + + rv = self.client.get(uri) + assert rv.status_code == 200 + response = json.loads(rv.data.decode("utf-8")) + roles = db.session.query(security_manager.role_model).all() + expected_roles = [str(role) for role in roles] + assert response["count"] == len(roles) + + response_roles = [result["text"] for result in response["result"]] + for expected_role in expected_roles: + assert expected_role in response_roles + + def test_get_filter_related_roles(self): + """ + API: Test get filter related roles + """ + self.login(username="admin") + argument = {"filter": "alpha"} + uri = f"api/v1/dashboard/related/roles?q={prison.dumps(argument)}" + + rv = self.client.get(uri) + assert rv.status_code == 200 + response = json.loads(rv.data.decode("utf-8")) + assert response["count"] == 1 + + response_roles = [result["text"] for result in response["result"]] + assert "Alpha" in response_roles