Skip to content
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 backend/src/baserow/contrib/database/api/views/gallery/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,11 @@
from baserow.contrib.database.views.filters import AdHocFilters
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.database.views.models import GalleryView
from baserow.contrib.database.views.operations import ListViewRowsOperationType
from baserow.contrib.database.views.registries import view_type_registry
from baserow.contrib.database.views.signals import view_loaded
from baserow.contrib.database.views.utils import check_permissions_with_view_fallback
from baserow.core.exceptions import UserNotInWorkspace
from baserow.core.handler import CoreHandler

from .errors import ERROR_GALLERY_DOES_NOT_EXIST
from .pagination import GalleryLimitOffsetPagination
Expand Down Expand Up @@ -207,12 +208,12 @@ def get(
)
view_type = view_type_registry.get_by_model(view)

workspace = view.table.database.workspace
CoreHandler().check_permissions(
request.user,
check_permissions_with_view_fallback(
ListRowsDatabaseTableOperationType.type,
workspace=workspace,
context=view.table,
ListViewRowsOperationType.type,
request.user,
view.table,
view,
)

search = query_params.get("search")
Expand Down
23 changes: 13 additions & 10 deletions backend/src/baserow/contrib/database/api/views/grid/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,14 @@
from baserow.contrib.database.views.filters import AdHocFilters
from baserow.contrib.database.views.handler import ViewHandler
from baserow.contrib.database.views.models import GridView
from baserow.contrib.database.views.operations import ListViewRowsOperationType
from baserow.contrib.database.views.registries import (
view_aggregation_type_registry,
view_type_registry,
)
from baserow.contrib.database.views.signals import view_loaded
from baserow.contrib.database.views.utils import check_permissions_with_view_fallback
from baserow.core.exceptions import UserNotInWorkspace
from baserow.core.handler import CoreHandler
from baserow.core.utils import split_comma_separated_string

from .errors import ERROR_GRID_DOES_NOT_EXIST
Expand Down Expand Up @@ -237,13 +238,14 @@ def get(self, request, view_id, field_options, row_metadata, query_params):
)
view_type = view_type_registry.get_by_model(view)

workspace = view.table.database.workspace
CoreHandler().check_permissions(
request.user,
check_permissions_with_view_fallback(
ListRowsDatabaseTableOperationType.type,
workspace=workspace,
context=view.table,
ListViewRowsOperationType.type,
request.user,
view.table,
view,
)

field_ids = get_include_exclude_field_ids(
view.table, include_fields, exclude_fields
)
Expand Down Expand Up @@ -347,11 +349,12 @@ def post(self, request, view_id, data):
"""

view = ViewHandler().get_view_as_user(request.user, view_id, GridView)
CoreHandler().check_permissions(
request.user,
check_permissions_with_view_fallback(
ListRowsDatabaseTableOperationType.type,
workspace=view.table.database.workspace,
context=view.table,
ListViewRowsOperationType.type,
request.user,
view.table,
view,
)
hidden_field_ids = get_hidden_field_ids_for_view_user(request.user, view)

Expand Down
2 changes: 2 additions & 0 deletions backend/src/baserow/contrib/database/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,7 @@ def ready(self):
ListViewFieldsOperationType,
ListViewFilterOperationType,
ListViewGroupByOperationType,
ListViewRowsOperationType,
ListViewsOperationType,
ListViewSortOperationType,
OrderViewsOperationType,
Expand Down Expand Up @@ -892,6 +893,7 @@ def ready(self):
operation_type_registry.register(ReadViewRowOperationType())
operation_type_registry.register(ReadAdjacentViewRowOperationType())
operation_type_registry.register(ListViewFieldsOperationType())
operation_type_registry.register(ListViewRowsOperationType())
operation_type_registry.register(CreateViewRowOperationType())
operation_type_registry.register(UpdateViewRowOperationType())
operation_type_registry.register(DeleteViewRowOperationType())
Expand Down
54 changes: 4 additions & 50 deletions backend/src/baserow/contrib/database/rows/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
from baserow.core.exceptions import (
CannotCalculateIntermediateOrder,
PermissionDenied,
PermissionException,
)
from baserow.core.handler import CoreHandler
from baserow.core.psycopg import is_unique_violation_error, sql
Expand Down Expand Up @@ -741,59 +740,14 @@ def _check_permissions_with_view_fallback(
:return:
"""

table_check = PermissionCheck(
user,
table_operation,
context=table,
)
view_check = PermissionCheck(
user,
view_operation,
context=view,
from baserow.contrib.database.views.utils import (
check_permissions_with_view_fallback,
)

checks = [table_check]
if view is not None:
checks.append(view_check)

# Check multiple permissions regardless because if a view is provided, we don't
# want to execute multiple queries in order to check if the permission check
# should fall back on the view.
check_results = CoreHandler().check_multiple_permissions(
checks,
workspace=table.database.workspace,
return_permissions_exceptions=True,
check_permissions_with_view_fallback(
table_operation, view_operation, user, table, view, row_ids
)

if check_results[table_check] is True:
return

if (
view is not None
# Because the user wants to change rows in a specific table, we must make
# sure that the provided view belongs to that table. Otherwise, it would
# result in a security bug.
and view.table_id == table.id
# The view ownership type should also allow modifying rows directly in
# the view. The rows are provided because some additional permission
# checks might need to be done in order to make sure that the user is
# allowed to modify the provided rows.
and view_ownership_type_registry.get(view.ownership_type).can_modify_rows(
view,
row_ids,
)
and check_results[view_check] is True
):
return

if isinstance(check_results[table_check], PermissionException):
raise check_results[table_check]

if isinstance(check_results[view_check], PermissionException):
raise check_results[view_check]

raise PermissionDenied(actor=user)

def create_row(
self,
user: AbstractUser,
Expand Down
4 changes: 4 additions & 0 deletions backend/src/baserow/contrib/database/views/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class ViewRowOperationType(OperationType, abc.ABC):
context_scope_name = DatabaseViewObjectScopeType.type


class ListViewRowsOperationType(ViewRowOperationType):
type = "database.table.view.list_rows"


class ReadViewRowOperationType(ViewRowOperationType):
type = "database.table.view.read_row"

Expand Down
95 changes: 94 additions & 1 deletion backend/src/baserow/contrib/database/views/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
from typing import Any, Dict, Tuple
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

from django.contrib.auth.models import AbstractUser
from django.db.models.aggregates import Aggregate, Count

from baserow.core.exceptions import PermissionDenied, PermissionException
from baserow.core.handler import CoreHandler
from baserow.core.registries import OperationType
from baserow.core.types import PermissionCheck

if TYPE_CHECKING:
from baserow.contrib.database.table.models import Table
from baserow.contrib.database.views.models import View


class AnnotatedAggregation:
"""
Expand Down Expand Up @@ -77,3 +87,86 @@ def serialize_row_for_action(row, model) -> Tuple[Dict[str, Any], Dict[str, Any]
if not f["type"].read_only
}
return serialized_values, fields_metadata


def check_permissions_with_view_fallback(
table_operation: OperationType,
view_operation: OperationType,
user: AbstractUser,
table: "Table",
view: Optional["View"],
row_ids: Optional[List[int]] = None,
):
"""
Checks if the user has permission to the provided table object. If not, it will
fall back to the view permissions, if the view ownership type allows it, then
check if the user has permissions to the view.

:param table_operation: The permission on table level to check. If this check
passes, then no exception will be raised.
:param view_operation: The permission on view level to check. If both this check
succeeds and the view ownership type allows it, then no exception will be
raised.
:param user: The user on whose behalf the permissions are checked.
:param table: The table where to check the permissions for.
:param view: Optionally provide the view where to check permissions for as
fallback.
:param row_ids: Optionally the row ids that are modified.
:raises PermissionDenied: If the user does not have access to both the table
and view.
"""

from baserow.contrib.database.views.registries import view_ownership_type_registry

table_check = PermissionCheck(
user,
table_operation,
context=table,
)
view_check = PermissionCheck(
user,
view_operation,
context=view,
)

checks = [table_check]
if view is not None:
checks.append(view_check)

# Check multiple permissions regardless because if a view is provided, we don't
# want to execute multiple queries in order to check if the permission check
# should fall back on the view.
check_results = CoreHandler().check_multiple_permissions(
checks,
workspace=table.database.workspace,
return_permissions_exceptions=True,
)

if check_results[table_check] is True:
return

if (
view is not None
# Because the user wants to access rows in a specific table, we must make
# sure that the provided view belongs to that table. Otherwise, it would
# result in a security bug.
and view.table_id == table.id
# The view ownership type should also allow accessing rows directly in
# the view. The rows are provided because some additional permission
# checks might need to be done in order to make sure that the user is
# allowed to access the provided rows.
and view_ownership_type_registry.get(view.ownership_type).can_modify_rows(
view,
row_ids,
)
and check_results[view_check] is True
):
return

if isinstance(check_results[table_check], PermissionException):
raise check_results[table_check]

if view is not None and isinstance(check_results[view_check], PermissionException):
raise check_results[view_check]

raise PermissionDenied(actor=user)
2 changes: 2 additions & 0 deletions backend/src/baserow/core/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def ready(self):
DeleteWorkspaceOperationType,
DeleteWorkspaceUserOperationType,
DuplicateApplicationOperationType,
ExportWorkspaceOperationType,
ListApplicationsWorkspaceOperationType,
ListInvitationsWorkspaceOperationType,
ListWorkspacesOperationType,
Expand Down Expand Up @@ -247,6 +248,7 @@ def ready(self):
operation_type_registry.register(ListWorkspacesOperationType())
operation_type_registry.register(UpdateWorkspaceInvitationType())
operation_type_registry.register(ReadWorkspaceOperationType())
operation_type_registry.register(ExportWorkspaceOperationType())
operation_type_registry.register(UpdateWorkspaceOperationType())
operation_type_registry.register(ListWorkspaceUsersWorkspaceOperationType())
operation_type_registry.register(OrderApplicationsOperationType())
Expand Down
4 changes: 2 additions & 2 deletions backend/src/baserow/core/import_export/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
ImportExportTrustedSource,
Workspace,
)
from baserow.core.operations import ReadWorkspaceOperationType
from baserow.core.operations import ExportWorkspaceOperationType
from baserow.core.registries import ImportExportConfig, application_type_registry
from baserow.core.signals import application_created, application_imported
from baserow.core.storage import (
Expand Down Expand Up @@ -99,7 +99,7 @@ def get_workspace_or_raise(self, user: AbstractUser, workspace_id: int):

core_handler.check_permissions(
user,
ReadWorkspaceOperationType.type,
ExportWorkspaceOperationType.type,
workspace=workspace,
context=workspace,
)
Expand Down
4 changes: 2 additions & 2 deletions backend/src/baserow/core/job_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
)
from baserow.core.operations import (
CreateApplicationsWorkspaceOperationType,
ExportWorkspaceOperationType,
ListApplicationsWorkspaceOperationType,
ReadWorkspaceOperationType,
)
from baserow.core.service import CoreService
from baserow.core.utils import Progress
Expand Down Expand Up @@ -300,7 +300,7 @@ def fetch_applications(self, user, workspace, application_ids):

CoreHandler().check_permissions(
user,
ReadWorkspaceOperationType.type,
ExportWorkspaceOperationType.type,
workspace=workspace,
context=workspace,
)
Expand Down
4 changes: 4 additions & 0 deletions backend/src/baserow/core/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class DeleteWorkspaceOperationType(WorkspaceCoreOperationType):
type = "workspace.delete"


class ExportWorkspaceOperationType(WorkspaceCoreOperationType):
type = "workspace.export"


class RestoreWorkspaceOperationType(WorkspaceCoreOperationType):
type = "workspace.restore"

Expand Down
Loading
Loading