Skip to content

Commit d3160f7

Browse files
authored
Merge pull request #1459 from weaviate/1.28/respond-to-rbac-surface-feedback
Rename `role -> role_name`, add `InsufficientPermissionsError`
2 parents e978abb + bbed519 commit d3160f7

File tree

6 files changed

+67
-45
lines changed

6 files changed

+67
-45
lines changed

integration/test_rbac.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def test_create_role(client_factory: ClientFactory, permissions, expected) -> No
157157
pytest.skip("This test requires Weaviate 1.28.0 or higher")
158158
try:
159159
client.roles.create(
160-
name=expected.name,
160+
role_name=expected.name,
161161
permissions=permissions,
162162
)
163163
role = client.roles.by_name(expected.name)
@@ -175,7 +175,7 @@ def test_add_permissions_to_existing(client_factory: ClientFactory) -> None:
175175
role_name = "ExistingRolePermissions"
176176
try:
177177
client.roles.create(
178-
name=role_name,
178+
role_name=role_name,
179179
permissions=Permissions.collections(collection="*", create_collection=True),
180180
)
181181
role = client.roles.by_name(role_name)
@@ -190,7 +190,7 @@ def test_add_permissions_to_existing(client_factory: ClientFactory) -> None:
190190
permissions=[
191191
Permissions.collections(collection="*", delete_collection=True),
192192
],
193-
role=role_name,
193+
role_name=role_name,
194194
)
195195

196196
role = client.roles.by_name(role_name)
@@ -212,7 +212,7 @@ def test_upsert_permissions(client_factory: ClientFactory) -> None:
212212
try:
213213
client.roles.add_permissions(
214214
permissions=Permissions.collections(collection="*", create_collection=True),
215-
role=role_name,
215+
role_name=role_name,
216216
)
217217

218218
role = client.roles.by_name(role_name)
@@ -232,7 +232,7 @@ def test_downsert_permissions(client_factory: ClientFactory) -> None:
232232
role_name = "ExistingRoleDownsert"
233233
try:
234234
client.roles.create(
235-
name=role_name,
235+
role_name=role_name,
236236
permissions=Permissions.collections(
237237
collection="*", create_collection=True, delete_collection=True
238238
),
@@ -248,7 +248,7 @@ def test_downsert_permissions(client_factory: ClientFactory) -> None:
248248

249249
client.roles.remove_permissions(
250250
permissions=Permissions.collections(collection="*", delete_collection=True),
251-
role=role_name,
251+
role_name=role_name,
252252
)
253253

254254
role = client.roles.by_name(role_name)
@@ -260,7 +260,7 @@ def test_downsert_permissions(client_factory: ClientFactory) -> None:
260260

261261
client.roles.remove_permissions(
262262
permissions=Permissions.collections(collection="*", create_collection=True),
263-
role=role_name,
263+
role_name=role_name,
264264
)
265265
role = client.roles.by_name(role_name)
266266
assert role is None
@@ -288,7 +288,7 @@ def test_multiple_permissions(client_factory: ClientFactory) -> None:
288288
]
289289

290290
client.roles.create(
291-
name=role_name,
291+
role_name=role_name,
292292
permissions=required_permissions,
293293
)
294294

mock_tests/test_collection.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,29 @@
3434
from weaviate.connect.base import ConnectionParams, ProtocolParams
3535
from weaviate.connect.integrations import _IntegrationConfig
3636
from weaviate.exceptions import (
37-
UnexpectedStatusCodeError,
3837
WeaviateStartUpError,
3938
BackupCanceledError,
39+
InsufficientPermissionsError,
4040
)
4141

4242
ACCESS_TOKEN = "HELLO!IamAnAccessToken"
4343
REFRESH_TOKEN = "UseMeToRefreshYourAccessToken"
4444

4545

46-
def test_status_code_exception(weaviate_mock: HTTPServer, start_grpc_server: grpc.Server) -> None:
47-
weaviate_mock.expect_request("/v1/schema/Test").respond_with_json(response_json={}, status=403)
46+
def test_insufficient_permissions(
47+
weaviate_mock: HTTPServer, start_grpc_server: grpc.Server
48+
) -> None:
49+
weaviate_mock.expect_request("/v1/schema/Test").respond_with_json(
50+
response_json={"error": [{"message": "this is an error"}]}, status=403
51+
)
4852

4953
client = weaviate.connect_to_local(
5054
port=MOCK_PORT, host=MOCK_IP, grpc_port=MOCK_PORT_GRPC, skip_init_checks=True
5155
)
5256
collection = client.collections.get("Test")
53-
with pytest.raises(UnexpectedStatusCodeError) as e:
57+
with pytest.raises(InsufficientPermissionsError) as e:
5458
collection.config.get()
55-
assert e.value.status_code == 403
59+
assert "this is an error" in e.value.message
5660
weaviate_mock.check_assertions()
5761

5862

weaviate/connect/v4.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
WeaviateGRPCUnavailableError,
5858
WeaviateStartUpError,
5959
WeaviateTimeoutError,
60+
InsufficientPermissionsError,
6061
)
6162
from weaviate.proto.v1 import weaviate_pb2_grpc
6263
from weaviate.util import (
@@ -474,6 +475,8 @@ async def __send(
474475
timeout=self.__get_timeout(method, is_gql_query),
475476
)
476477
res = await self._client.send(req)
478+
if res.status_code == 403:
479+
raise InsufficientPermissionsError(res)
477480
if status_codes is not None and res.status_code not in status_codes.ok:
478481
raise UnexpectedStatusCodeError(error_msg, response=res)
479482
return cast(Response, res)

weaviate/exceptions.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,12 @@ class WeaviateRetryError(WeaviateBaseError):
358358
def __init__(self, message: str, count: int) -> None:
359359
msg = f"""The request to Weaviate failed after {count} retries. Details: {message}"""
360360
super().__init__(msg)
361+
362+
363+
class InsufficientPermissionsError(WeaviateBaseError):
364+
"""Is raised when a request to Weaviate fails due to insufficient permissions."""
365+
366+
def __init__(self, res: httpx.Response) -> None:
367+
err = res.json()["error"][0]["message"]
368+
msg = f"""The request to Weaviate failed due to insufficient permissions. Details: {err}"""
369+
super().__init__(msg)

weaviate/rbac/roles.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,27 +152,27 @@ async def get_current_roles(self) -> Dict[str, Role]:
152152
role["name"]: Role._from_weaviate_role(role) for role in await self._get_current_roles()
153153
}
154154

155-
async def exists(self, role: str) -> bool:
155+
async def exists(self, role_name: str) -> bool:
156156
"""Check if a role exists.
157157
158158
Args:
159-
role: The name of the role to check.
159+
role_name: The name of the role to check.
160160
161161
Returns:
162162
True if the role exists, False otherwise.
163163
"""
164-
return await self._get_role(role) is not None
164+
return await self._get_role(role_name) is not None
165165

166-
async def by_name(self, role: str) -> Optional[Role]:
166+
async def by_name(self, role_name: str) -> Optional[Role]:
167167
"""Get the permissions granted to this role.
168168
169169
Args:
170-
role: The name of the role to get the permissions for.
170+
role_name: The name of the role to get the permissions for.
171171
172172
Returns:
173173
A `Role` object or `None` if it does not exist.
174174
"""
175-
r = await self._get_role(role)
175+
r = await self._get_role(role_name)
176176
if r is None:
177177
return None
178178
return Role._from_weaviate_role(r)
@@ -191,54 +191,56 @@ async def by_user(self, user: str) -> Dict[str, Role]:
191191
for role in await self._get_roles_of_user(user)
192192
}
193193

194-
async def users(self, role: str) -> Dict[str, User]:
194+
async def users(self, user_name: str) -> Dict[str, User]:
195195
"""Get the users that have been assigned this role.
196196
197197
Args:
198-
role: The role to get the users for.
198+
user_name: The role to get the users for.
199199
200200
Returns:
201201
A dictionary with user names as keys and the `User` objects as values.
202202
"""
203203
return {
204204
user: self.__user_from_weaviate_user(user)
205-
for user in await self._get_users_of_role(role)
205+
for user in await self._get_users_of_role(user_name)
206206
}
207207

208-
async def delete(self, role: str) -> None:
208+
async def delete(self, role_name: str) -> None:
209209
"""Delete a role.
210210
211211
Args:
212-
role: The name of the role to delete.
212+
role_name: The name of the role to delete.
213213
"""
214-
return await self._delete_role(role)
214+
return await self._delete_role(role_name)
215215

216-
async def create(self, *, name: str, permissions: PermissionsInputType) -> Role:
216+
async def create(self, *, role_name: str, permissions: PermissionsInputType) -> Role:
217217
"""Create a new role.
218218
219219
Args:
220-
name: The name of the role.
220+
role_name: The name of the role.
221221
permissions: The permissions of the role.
222222
223223
Returns:
224224
The created role.
225225
"""
226226
role: WeaviateRole = {
227-
"name": name,
227+
"name": role_name,
228228
"permissions": [
229229
permission._to_weaviate() for permission in _flatten_permissions(permissions)
230230
],
231231
}
232232
return Role._from_weaviate_role(await self._post_roles(role))
233233

234-
async def assign(self, *, roles: Union[str, List[str]], user: str) -> None:
234+
async def assign(self, *, role_names: Union[str, List[str]], user: str) -> None:
235235
"""Assign roles to a user.
236236
237237
Args:
238-
roles: The roles to assign to the user.
238+
role_names: The names of the roles to assign to the user.
239239
user: The user to assign the roles to.
240240
"""
241-
await self._assign_roles_to_user([roles] if isinstance(roles, str) else roles, user)
241+
await self._assign_roles_to_user(
242+
[role_names] if isinstance(role_names, str) else role_names, user
243+
)
242244

243245
async def revoke(self, *, roles: Union[str, List[str]], user: str) -> None:
244246
"""Revoke roles from a user.
@@ -249,34 +251,38 @@ async def revoke(self, *, roles: Union[str, List[str]], user: str) -> None:
249251
"""
250252
await self._revoke_roles_from_user([roles] if isinstance(roles, str) else roles, user)
251253

252-
async def add_permissions(self, *, permissions: PermissionsInputType, role: str) -> None:
254+
async def add_permissions(self, *, permissions: PermissionsInputType, role_name: str) -> None:
253255
"""Add permissions to a role.
254256
255257
Note: This method is an upsert operation. If the permission already exists, it will be updated. If it does not exist, it will be created.
256258
257259
Args:
258260
permissions: The permissions to add to the role.
259-
role: The role to add the permissions to.
261+
role_name: The name of the role to add the permissions to.
260262
"""
261263
if isinstance(permissions, _Permission):
262264
permissions = [permissions]
263265
await self._add_permissions(
264-
[permission._to_weaviate() for permission in _flatten_permissions(permissions)], role
266+
[permission._to_weaviate() for permission in _flatten_permissions(permissions)],
267+
role_name,
265268
)
266269

267-
async def remove_permissions(self, *, permissions: PermissionsInputType, role: str) -> None:
270+
async def remove_permissions(
271+
self, *, permissions: PermissionsInputType, role_name: str
272+
) -> None:
268273
"""Remove permissions from a role.
269274
270275
Note: This method is a downsert operation. If the permission does not exist, it will be ignored. If these permissions are the only permissions of the role, the role will be deleted.
271276
272277
Args:
273278
permissions: The permissions to remove from the role.
274-
role: The role to remove the permissions from.
279+
role_name: The name of the role to remove the permissions from.
275280
"""
276281
if isinstance(permissions, _Permission):
277282
permissions = [permissions]
278283
await self._remove_permissions(
279-
[permission._to_weaviate() for permission in _flatten_permissions(permissions)], role
284+
[permission._to_weaviate() for permission in _flatten_permissions(permissions)],
285+
role_name,
280286
)
281287

282288

weaviate/rbac/sync.pyi

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ from weaviate.rbac.roles import _RolesBase
66
class _Roles(_RolesBase):
77
def list_all(self) -> Dict[str, Role]: ...
88
def get_current_roles(self) -> Dict[str, Role]: ...
9-
def by_name(self, role: str) -> Optional[Role]: ...
9+
def by_name(self, role_name: str) -> Optional[Role]: ...
1010
def by_user(self, user: str) -> Dict[str, Role]: ...
11-
def users(self, role: str) -> Dict[str, User]: ...
12-
def delete(self, role: str) -> None: ...
13-
def create(self, *, name: str, permissions: PermissionsInputType) -> Role: ...
14-
def assign(self, *, roles: Union[str, List[str]], user: str) -> None: ...
15-
def revoke(self, *, roles: Union[str, List[str]], user: str) -> None: ...
16-
def add_permissions(self, *, permissions: PermissionsInputType, role: str) -> None: ...
17-
def remove_permissions(self, *, permissions: PermissionsInputType, role: str) -> None: ...
11+
def users(self, role_name: str) -> Dict[str, User]: ...
12+
def delete(self, role_name: str) -> None: ...
13+
def create(self, *, role_name: str, permissions: PermissionsInputType) -> Role: ...
14+
def assign(self, *, role_names: Union[str, List[str]], user: str) -> None: ...
15+
def revoke(self, *, role_names: Union[str, List[str]], user: str) -> None: ...
16+
def add_permissions(self, *, permissions: PermissionsInputType, role_name: str) -> None: ...
17+
def remove_permissions(self, *, permissions: PermissionsInputType, role_name: str) -> None: ...

0 commit comments

Comments
 (0)