mirror of https://github.com/apache/superset.git
feat(dashboard_rbac): add support for related roles (#13035)
This commit is contained in:
parent
10296651fb
commit
312cbf736c
|
@ -54,6 +54,7 @@ from superset.dashboards.filters import (
|
||||||
DashboardFavoriteFilter,
|
DashboardFavoriteFilter,
|
||||||
DashboardFilter,
|
DashboardFilter,
|
||||||
DashboardTitleOrSlugFilter,
|
DashboardTitleOrSlugFilter,
|
||||||
|
FilterRelatedRoles,
|
||||||
)
|
)
|
||||||
from superset.dashboards.schemas import (
|
from superset.dashboards.schemas import (
|
||||||
DashboardPostSchema,
|
DashboardPostSchema,
|
||||||
|
@ -192,12 +193,14 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
||||||
order_rel_fields = {
|
order_rel_fields = {
|
||||||
"slices": ("slice_name", "asc"),
|
"slices": ("slice_name", "asc"),
|
||||||
"owners": ("first_name", "asc"),
|
"owners": ("first_name", "asc"),
|
||||||
|
"roles": ("name", "asc"),
|
||||||
}
|
}
|
||||||
related_field_filters = {
|
related_field_filters = {
|
||||||
"owners": RelatedFieldFilter("first_name", FilterRelatedOwners),
|
"owners": RelatedFieldFilter("first_name", FilterRelatedOwners),
|
||||||
|
"roles": RelatedFieldFilter("name", FilterRelatedRoles),
|
||||||
"created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
|
"created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
|
||||||
}
|
}
|
||||||
allowed_rel_fields = {"owners", "created_by"}
|
allowed_rel_fields = {"owners", "roles", "created_by"}
|
||||||
|
|
||||||
openapi_spec_tag = "Dashboards"
|
openapi_spec_tag = "Dashboards"
|
||||||
""" Override the name set for this collection of endpoints """
|
""" Override the name set for this collection of endpoints """
|
||||||
|
|
|
@ -14,7 +14,9 @@
|
||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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_appbuilder.security.sqla.models import Role
|
||||||
from flask_babel import lazy_gettext as _
|
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
|
from superset.views.base_api import BaseFavoriteFilter
|
||||||
|
|
||||||
|
|
||||||
class DashboardTitleOrSlugFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
class DashboardTitleOrSlugFilter(BaseFilter):
|
||||||
name = _("Title or Slug")
|
name = _("Title or Slug")
|
||||||
arg_name = "title_or_slug"
|
arg_name = "title_or_slug"
|
||||||
|
|
||||||
|
@ -45,9 +47,7 @@ class DashboardTitleOrSlugFilter(BaseFilter): # pylint: disable=too-few-public-
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DashboardFavoriteFilter(
|
class DashboardFavoriteFilter(BaseFavoriteFilter):
|
||||||
BaseFavoriteFilter
|
|
||||||
): # pylint: disable=too-few-public-methods
|
|
||||||
"""
|
"""
|
||||||
Custom filter for the GET list that filters all dashboards that a user has favored
|
Custom filter for the GET list that filters all dashboards that a user has favored
|
||||||
"""
|
"""
|
||||||
|
@ -57,7 +57,7 @@ class DashboardFavoriteFilter(
|
||||||
model = Dashboard
|
model = Dashboard
|
||||||
|
|
||||||
|
|
||||||
class DashboardFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
class DashboardFilter(BaseFilter):
|
||||||
"""
|
"""
|
||||||
List dashboards with the following criteria:
|
List dashboards with the following criteria:
|
||||||
1. Those which the user owns
|
1. Those which the user owns
|
||||||
|
@ -138,3 +138,23 @@ class DashboardFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||||
)
|
)
|
||||||
|
|
||||||
return query
|
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
|
||||||
|
|
|
@ -29,7 +29,7 @@ import yaml
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
from freezegun import freeze_time
|
from freezegun import freeze_time
|
||||||
from sqlalchemy import and_, or_
|
from sqlalchemy import and_
|
||||||
from superset import db, security_manager
|
from superset import db, security_manager
|
||||||
from superset.models.dashboard import Dashboard
|
from superset.models.dashboard import Dashboard
|
||||||
from superset.models.core import FavStar, FavStarClassName
|
from superset.models.core import FavStar, FavStarClassName
|
||||||
|
@ -1343,3 +1343,37 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
||||||
assert response == {
|
assert response == {
|
||||||
"message": {"metadata.yaml": {"type": ["Must be equal to Dashboard."]}}
|
"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
|
||||||
|
|
Loading…
Reference in New Issue