Skip to content

Commit e97910c

Browse files
authored
Handle PermissionDenied when listing accessible workspaces (#2733)
## Changes Handle `PermissionDenied` when listing accessible workspaces ### Linked issues Resolves #2732 ### Functionality - [x] modified existing command: that user the accesible workspaces (most account level commands) ### Tests - [ ] manually tested - [x] added unit tests
1 parent 70abd21 commit e97910c

File tree

2 files changed

+72
-12
lines changed

2 files changed

+72
-12
lines changed

src/databricks/labs/ucx/account/workspaces.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,25 @@ def get_accessible_workspaces(self) -> list[Workspace]:
9898
return accessible_workspaces
9999

100100
def can_administer(self, workspace: Workspace) -> bool:
101+
"""Evaluate if the user can administer a workspace.
102+
103+
A user can administer a workspace if the user can access the workspace and is a member of the workspace "admins"
104+
group.
105+
106+
Args:
107+
workspace (Workspace): The workspace to check if the user can administer.
108+
109+
Returns:
110+
bool: True if the user can administer the workspace, False otherwise.
111+
"""
101112
try:
102-
# check if user has access to workspace
103113
ws = self.client_for(workspace)
104-
except (PermissionDenied, NotFound, ValueError) as err:
105-
logger.warning(f"{workspace.deployment_name}: Encounter error {err}. Skipping...")
106-
return False
107-
current_user = ws.current_user.me()
108-
if current_user.groups is None:
114+
current_user = ws.current_user.me()
115+
except (PermissionDenied, NotFound, ValueError) as e:
116+
logger.warning(f"User cannot access workspace: {workspace.deployment_name}", exc_info=e)
109117
return False
110-
# check if user is a workspace admin
111-
if "admins" not in [g.display for g in current_user.groups]:
112-
logger.warning(
113-
f"{workspace.deployment_name}: User {current_user.user_name} is not a workspace admin. Skipping..."
114-
)
118+
if current_user.groups is None or "admins" not in {g.display for g in current_user.groups}:
119+
logger.warning(f"User '{current_user.user_name}' is not a workspace admin: {workspace.deployment_name}")
115120
return False
116121
return True
117122

tests/unit/account/test_workspaces.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import io
23
import json
34
from unittest.mock import create_autospec
@@ -6,7 +7,7 @@
67
from databricks.labs.blueprint.installation import Installation, MockInstallation
78
from databricks.labs.blueprint.tui import MockPrompts
89
from databricks.sdk import AccountClient, WorkspaceClient
9-
from databricks.sdk.errors import NotFound, ResourceConflict
10+
from databricks.sdk.errors import NotFound, PermissionDenied, ResourceConflict
1011
from databricks.sdk.service import iam
1112
from databricks.sdk.service.iam import ComplexValue, Group, ResourceMeta, User
1213
from databricks.sdk.service.provisioning import Workspace
@@ -494,3 +495,57 @@ def get_workspace_client(workspace) -> WorkspaceClient:
494495
acc.config.auth_type = "databricks-cli"
495496
account_workspaces = AccountWorkspaces(acc)
496497
assert len(account_workspaces.get_accessible_workspaces()) == 1
498+
499+
500+
def test_account_workspaces_can_administer_when_user_in_admins_group() -> None:
501+
acc = create_autospec(AccountClient)
502+
ws = create_autospec(WorkspaceClient)
503+
acc.get_workspace_client.return_value = ws
504+
ws.current_user.me.return_value = User(user_name="test", groups=[ComplexValue(display="admins")])
505+
account_workspaces = AccountWorkspaces(acc)
506+
workspace = Workspace(deployment_name="test")
507+
508+
assert account_workspaces.can_administer(workspace)
509+
510+
511+
@pytest.mark.parametrize("groups", [[ComplexValue(display="not-admins")], None])
512+
def test_account_workspaces_cannot_administer_when_user_not_in_admins_group(caplog, groups) -> None:
513+
acc = create_autospec(AccountClient)
514+
ws = create_autospec(WorkspaceClient)
515+
acc.get_workspace_client.return_value = ws
516+
ws.current_user.me.return_value = User(user_name="test", groups=groups)
517+
account_workspaces = AccountWorkspaces(acc)
518+
workspace = Workspace(deployment_name="test")
519+
520+
with caplog.at_level(logging.WARNING, logger="databricks.labs.ucx.account.workspaces"):
521+
can_administer = account_workspaces.can_administer(workspace)
522+
assert not can_administer
523+
assert "User 'test' is not a workspace admin: test" in caplog.messages
524+
525+
526+
def test_account_workspaces_can_administer_handles_not_found_error_for_get_workspace_client(caplog) -> None:
527+
acc = create_autospec(AccountClient)
528+
acc.get_workspace_client.side_effect = NotFound
529+
account_workspaces = AccountWorkspaces(acc)
530+
workspace = Workspace(deployment_name="test")
531+
532+
with caplog.at_level(logging.WARNING, logger="databricks.labs.ucx.account.workspaces"):
533+
can_administer = account_workspaces.can_administer(workspace)
534+
assert not can_administer
535+
assert "User cannot access workspace: test" in caplog.messages
536+
537+
538+
def test_account_workspaces_can_administer_handles_permission_denied_error_for_current_user(caplog) -> None:
539+
acc = create_autospec(AccountClient)
540+
ws = create_autospec(WorkspaceClient)
541+
acc.get_workspace_client.return_value = ws
542+
ws.current_user.me.side_effect = PermissionDenied(
543+
"This API is disabled for users without the databricks-sql-access or workspace-access entitlements"
544+
)
545+
account_workspaces = AccountWorkspaces(acc)
546+
workspace = Workspace(deployment_name="test")
547+
548+
with caplog.at_level(logging.WARNING, logger="databricks.labs.ucx.account.workspaces"):
549+
can_administer = account_workspaces.can_administer(workspace)
550+
assert not can_administer
551+
assert "User cannot access workspace: test" in caplog.messages

0 commit comments

Comments
 (0)